Merge pull request #9626 from featheredtoast/add-proxmox-vm-clone

builder/proxmox FEATURE: split Proxmox into proxmox-iso and proxmox-clone
This commit is contained in:
Megan Marsh 2020-10-23 13:36:45 -07:00 committed by GitHub
commit fc619dc977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1805 additions and 467 deletions

View File

@ -1,191 +1,5 @@
package proxmox
import (
"context"
"crypto/tls"
"errors"
"fmt"
import proxmoxiso "github.com/hashicorp/packer/builder/proxmox/iso"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// The unique id for the builder
const BuilderId = "proxmox.builder"
type Builder struct {
config Config
runner multistep.Runner
proxmoxClient *proxmox.Client
}
// Builder implements packer.Builder
var _ packer.Builder = &Builder{}
var pluginVersion = "1.0.0"
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
warnings, errs := b.config.Prepare(raws...)
if errs != nil {
return nil, warnings, errs
}
return nil, nil, nil
}
const downloadPathKey = "downloaded_iso_path"
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
var err error
tlsConfig := &tls.Config{
InsecureSkipVerify: b.config.SkipCertValidation,
}
b.proxmoxClient, err = proxmox.NewClient(b.config.proxmoxURL.String(), nil, tlsConfig)
if err != nil {
return nil, err
}
err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "")
if err != nil {
return nil, err
}
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("proxmoxClient", b.proxmoxClient)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&common.StepDownload{
Checksum: b.config.ISOChecksum,
Description: "ISO",
Extension: b.config.TargetExtension,
ResultKey: downloadPathKey,
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
}}
for idx := range b.config.AdditionalISOFiles {
steps = append(steps, &common.StepDownload{
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
Description: "additional ISO",
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey,
TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey,
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
})
}
steps = append(steps,
&stepUploadISO{},
&stepUploadAdditionalISOs{},
&stepStartVM{},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
&stepTypeBootCommand{
BootConfig: b.config.BootConfig,
Ctx: b.config.ctx,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: commHost(b.config.Comm.Host()),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Comm,
},
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepSuccess{},
)
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("build was cancelled")
}
// Verify that the template_id was set properly, otherwise we didn't progress through the last step
tplID, ok := state.Get("template_id").(int)
if !ok {
return nil, fmt.Errorf("template ID could not be determined")
}
artifact := &Artifact{
templateID: tplID,
proxmoxClient: b.proxmoxClient,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}
// Returns ssh_host or winrm_host (see communicator.Config.Host) config
// parameter when set, otherwise gets the host IP from running VM
func commHost(host string) func(state multistep.StateBag) (string, error) {
if host != "" {
return func(state multistep.StateBag) (string, error) {
return host, nil
}
}
return getVMIP
}
// Reads the first non-loopback interface's IP address from the VM.
// qemu-guest-agent package must be installed on the VM
func getVMIP(state multistep.StateBag) (string, error) {
client := state.Get("proxmoxClient").(*proxmox.Client)
config := state.Get("config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
if err != nil {
return "", err
}
if config.VMInterface != "" {
for _, iface := range ifs {
if config.VMInterface != iface.Name {
continue
}
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
}
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
}
for _, iface := range ifs {
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
}
return "", fmt.Errorf("Found no IP addresses on VM")
}
type Builder = proxmoxiso.Builder

View File

@ -0,0 +1,74 @@
package proxmoxclone
import (
proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/hcl/v2/hcldec"
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"context"
"fmt"
)
// The unique id for the builder
const BuilderID = "proxmox.clone"
type Builder struct {
config Config
}
// Builder implements packer.Builder
var _ packer.Builder = &Builder{}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return b.config.Prepare(raws...)
}
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)
preSteps := []multistep.Step{
&StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
},
}
postSteps := []multistep.Step{}
sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps, &cloneVMCreator{})
return sb.Run(ctx, ui, hook, state)
}
type cloneVMCreator struct{}
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("config").(*proxmox.Config).Comm
fullClone := 1
if c.FullClone.False() {
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
}
return nil
}

View File

@ -0,0 +1,29 @@
//go:generate mapstructure-to-hcl2 -type Config
package proxmoxclone
import (
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
)
type Config struct {
proxmox.Config `mapstructure:",squash"`
CloneVM string `mapstructure:"clone_vm"`
FullClone config.Trilean `mapstructure:"full_clone" required:"false"`
}
func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) {
var errs *packer.MultiError
_, warnings, merrs := c.Config.Prepare(c, raws...)
if merrs != nil {
errs = packer.MultiErrorAppend(errs, merrs)
}
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
return nil, warnings, nil
}

View File

@ -0,0 +1,211 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package proxmoxclone
import (
"github.com/hashicorp/hcl/v2/hcldec"
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []proxmox.FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []proxmox.FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
CloneVM *string `mapstructure:"clone_vm" cty:"clone_vm" hcl:"clone_vm"`
FullClone *bool `mapstructure:"full_clone" required:"false" cty:"full_clone" hcl:"full_clone"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"proxmox_url": &hcldec.AttrSpec{Name: "proxmox_url", Type: cty.String, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"vm_id": &hcldec.AttrSpec{Name: "vm_id", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
"cpu_type": &hcldec.AttrSpec{Name: "cpu_type", Type: cty.String, Required: false},
"sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false},
"os": &hcldec.AttrSpec{Name: "os", Type: cty.String, Required: false},
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())},
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatnicConfig)(nil).HCL2Spec())},
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())},
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatstorageConfig)(nil).HCL2Spec())},
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", 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"
common "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/hashicorp/packer/common/uuid"
"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
}
func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*common.Config)
if c.Comm.SSHPassword != "" {
return multistep.ActionContinue
}
if c.Comm.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key for the communicator...")
privateKeyBytes, err := c.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
}
c.Comm.SSHPrivateKey = privateKeyBytes
c.Comm.SSHKeyPairName = kp.Comment
c.Comm.SSHTemporaryKeyPairName = kp.Comment
c.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
return multistep.ActionContinue
}
if c.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
}
c.Comm.SSHKeyPairName = kp.Comment
c.Comm.SSHTemporaryKeyPairName = kp.Comment
c.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock
c.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
c.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

@ -10,6 +10,7 @@ import (
)
type Artifact struct {
builderID string
templateID int
proxmoxClient *proxmox.Client
@ -21,8 +22,8 @@ type Artifact struct {
// Artifact implements packer.Artifact
var _ packer.Artifact = &Artifact{}
func (*Artifact) BuilderId() string {
return BuilderId
func (a *Artifact) BuilderId() string {
return a.builderID
}
func (*Artifact) Files() []string {

View File

@ -0,0 +1,167 @@
package proxmox
import (
"context"
"crypto/tls"
"errors"
"fmt"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func NewSharedBuilder(id string, config Config, preSteps []multistep.Step, postSteps []multistep.Step, vmCreator ProxmoxVMCreator) *Builder {
return &Builder{
id: id,
config: config,
preSteps: preSteps,
postSteps: postSteps,
vmCreator: vmCreator,
}
}
type Builder struct {
id string
config Config
preSteps []multistep.Step
postSteps []multistep.Step
runner multistep.Runner
proxmoxClient *proxmox.Client
vmCreator ProxmoxVMCreator
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook, state multistep.StateBag) (packer.Artifact, error) {
var err error
tlsConfig := &tls.Config{
InsecureSkipVerify: b.config.SkipCertValidation,
}
b.proxmoxClient, err = proxmox.NewClient(b.config.proxmoxURL.String(), nil, tlsConfig)
if err != nil {
return nil, err
}
err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "")
if err != nil {
return nil, err
}
// Set up the state
state.Put("config", &b.config)
state.Put("proxmoxClient", b.proxmoxClient)
state.Put("hook", hook)
state.Put("ui", ui)
comm := &b.config.Comm
// Build the steps
coreSteps := []multistep.Step{
&stepStartVM{
vmCreator: b.vmCreator,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
&stepTypeBootCommand{
BootConfig: b.config.BootConfig,
Ctx: b.config.Ctx,
},
&communicator.StepConnect{
Config: comm,
Host: commHost((*comm).Host()),
SSHConfig: (*comm).SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Comm,
},
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepSuccess{},
}
steps := append(b.preSteps, coreSteps...)
steps = append(steps, b.postSteps...)
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("build was cancelled")
}
// Verify that the template_id was set properly, otherwise we didn't progress through the last step
tplID, ok := state.Get("template_id").(int)
if !ok {
return nil, fmt.Errorf("template ID could not be determined")
}
artifact := &Artifact{
builderID: b.id,
templateID: tplID,
proxmoxClient: b.proxmoxClient,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}
// Returns ssh_host or winrm_host (see communicator.Config.Host) config
// parameter when set, otherwise gets the host IP from running VM
func commHost(host string) func(state multistep.StateBag) (string, error) {
if host != "" {
return func(state multistep.StateBag) (string, error) {
return host, nil
}
}
return getVMIP
}
// Reads the first non-loopback interface's IP address from the VM.
// qemu-guest-agent package must be installed on the VM
func getVMIP(state multistep.StateBag) (string, error) {
client := state.Get("proxmoxClient").(*proxmox.Client)
config := state.Get("config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
if err != nil {
return "", err
}
if config.VMInterface != "" {
for _, iface := range ifs {
if config.VMInterface != iface.Name {
continue
}
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
}
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
}
for _, iface := range ifs {
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
}
return "", fmt.Errorf("Found no IP addresses on VM")
}

View File

@ -8,7 +8,6 @@ import (
"log"
"net/url"
"os"
"strconv"
"strings"
"time"
@ -25,7 +24,6 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
BootKeyInterval time.Duration `mapstructure:"boot_key_interval"`
Comm communicator.Config `mapstructure:",squash"`
@ -49,8 +47,6 @@ type Config struct {
VGA vgaConfig `mapstructure:"vga"`
NICs []nicConfig `mapstructure:"network_adapters"`
Disks []diskConfig `mapstructure:"disks"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Agent bool `mapstructure:"qemu_agent"`
SCSIController string `mapstructure:"scsi_controller"`
Onboot bool `mapstructure:"onboot"`
@ -58,17 +54,24 @@ type Config struct {
TemplateName string `mapstructure:"template_name"`
TemplateDescription string `mapstructure:"template_description"`
UnmountISO bool `mapstructure:"unmount_iso"`
CloudInit bool `mapstructure:"cloud_init"`
CloudInitStoragePool string `mapstructure:"cloud_init_storage_pool"`
shouldUploadISO bool
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
VMInterface string `mapstructure:"vm_interface"`
ctx interpolate.Context
Ctx interpolate.Context `mapstructure-to-hcl2:",skip"`
}
type storageConfig struct {
common.ISOConfig `mapstructure:",squash"`
Device string `mapstructure:"device"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Unmount bool `mapstructure:"unmount"`
ShouldUploadISO bool
DownloadPathKey string
}
type nicConfig struct {
@ -92,27 +95,18 @@ type vgaConfig struct {
Type string `mapstructure:"type"`
Memory int `mapstructure:"memory"`
}
type storageConfig struct {
common.ISOConfig `mapstructure:",squash"`
Device string `mapstructure:"device"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Unmount bool `mapstructure:"unmount"`
shouldUploadISO bool
downloadPathKey string
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []string, error) {
// Agent defaults to true
c.Agent = true
// Do not add a cloud-init cdrom by default
c.CloudInit = false
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
err := config.Decode(upper, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateContext: &c.Ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
@ -120,11 +114,13 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
},
}, raws...)
if err != nil {
return nil, err
return nil, nil, err
}
var errs *packer.MultiError
warnings := make([]string, 0)
var warnings []string
packer.LogSecretFilter.Set(c.Password)
// Defaults
if c.ProxmoxURLRaw == "" {
@ -208,89 +204,14 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType))
}
}
for idx := range c.AdditionalISOFiles {
// Check AdditionalISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.AdditionalISOFiles[idx].ISOFile != "" {
c.AdditionalISOFiles[idx].shouldUploadISO = false
} else {
c.AdditionalISOFiles[idx].downloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx)
isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.AdditionalISOFiles[idx].shouldUploadISO = true
}
if c.AdditionalISOFiles[idx].Device == "" {
log.Printf("AdditionalISOFile %d Device not set, using default 'ide3'", idx)
c.AdditionalISOFiles[idx].Device = "ide3"
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "ide") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[3:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:]))
}
if busnumber == 2 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO"))
}
if busnumber > 3 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "sata") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 5 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "scsi") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 30 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30"))
}
}
if (c.AdditionalISOFiles[idx].ISOFile == "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0) || (c.AdditionalISOFiles[idx].ISOFile != "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
}
if c.SCSIController == "" {
log.Printf("SCSI controller not set, using default 'lsi'")
c.SCSIController = "lsi"
}
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
// Check ISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.ISOFile != "" {
c.shouldUploadISO = false
} else {
isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.shouldUploadISO = true
}
if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified"))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.Ctx)...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.Ctx)...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.Ctx)...)
// Required configurations that will display errors if not set
if c.Username == "" {
@ -329,11 +250,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
return nil, warnings, errs
}
packer.LogSecretFilter.Set(c.Password)
return nil, nil
return nil, warnings, nil
}
func contains(haystack []string, needle string) bool {

View File

@ -21,11 +21,6 @@ type FlatConfig struct {
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
@ -93,15 +88,12 @@ type FlatConfig struct {
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
@ -132,11 +124,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
@ -204,15 +191,12 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*FlatvgaConfig)(nil).HCL2Spec())},
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatnicConfig)(nil).HCL2Spec())},
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())},
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
@ -301,6 +285,8 @@ type FlatstorageConfig struct {
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
ShouldUploadISO *bool `cty:"should_upload_iso" hcl:"should_upload_iso"`
DownloadPathKey *string `cty:"download_path_key" hcl:"download_path_key"`
}
// FlatMapstructure returns a new FlatstorageConfig.
@ -324,6 +310,8 @@ func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec {
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
"should_upload_iso": &hcldec.AttrSpec{Name: "should_upload_iso", Type: cty.Bool, Required: false},
"download_path_key": &hcldec.AttrSpec{Name: "download_path_key", Type: cty.String, Required: false},
}
return s
}

View File

@ -0,0 +1,104 @@
package proxmox
import (
"strings"
"testing"
"github.com/hashicorp/packer/packer"
)
func mandatoryConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"username": "apiuser@pve",
"password": "supersecret",
"node": "my-proxmox",
"ssh_username": "root",
}
}
func TestRequiredParameters(t *testing.T) {
var c Config
_, _, err := c.Prepare(&c, make(map[string]interface{}))
if err == nil {
t.Fatal("Expected empty configuration to fail")
}
errs, ok := err.(*packer.MultiError)
if !ok {
t.Fatal("Expected errors to be packer.MultiError")
}
required := []string{"username", "password", "proxmox_url", "node", "ssh_username"}
for _, param := range required {
found := false
for _, err := range errs.Errors {
if strings.Contains(err.Error(), param) {
found = true
break
}
}
if !found {
t.Errorf("Expected error about missing parameter %q", required)
}
}
}
func TestAgentSetToFalse(t *testing.T) {
cfg := mandatoryConfig(t)
cfg["qemu_agent"] = false
var c Config
_, _, err := c.Prepare(&c, cfg)
if err != nil {
t.Fatal(err)
}
if c.Agent != false {
t.Errorf("Expected Agent to be false, got %t", c.Agent)
}
}
func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
drivertests := []struct {
expectedToFail bool
model string
}{
{expectedToFail: false, model: "virtio"},
{expectedToFail: true, model: "e1000"},
{expectedToFail: true, model: "e1000-82540em"},
{expectedToFail: true, model: "e1000-82544gc"},
{expectedToFail: true, model: "e1000-82545em"},
{expectedToFail: true, model: "i82551"},
{expectedToFail: true, model: "i82557b"},
{expectedToFail: true, model: "i82559er"},
{expectedToFail: true, model: "ne2k_isa"},
{expectedToFail: true, model: "ne2k_pci"},
{expectedToFail: true, model: "pcnet"},
{expectedToFail: true, model: "rtl8139"},
{expectedToFail: true, model: "vmxnet3"},
}
for _, tt := range drivertests {
device := make(map[string]interface{})
device["bridge"] = "vmbr0"
device["model"] = tt.model
device["packet_queues"] = 2
devices := make([]map[string]interface{}, 0)
devices = append(devices, device)
cfg := mandatoryConfig(t)
cfg["network_adapters"] = devices
var c Config
_, _, err := c.Prepare(&c, cfg)
if tt.expectedToFail == true && err == nil {
t.Error("expected config preparation to fail, but no error occured")
}
if tt.expectedToFail == false && err != nil {
t.Errorf("expected config preparation to succeed, but %s", err.Error())
}
}
}

View File

@ -38,24 +38,6 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
// set, we need to clear it
changes["description"] = c.TemplateDescription
if c.UnmountISO {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if vmParams["ide2"] == nil || !strings.HasSuffix(vmParams["ide2"].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, ide2 is not present, or not a cdrom media")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes["ide2"] = "none,media=cdrom"
}
if c.CloudInit {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
@ -93,29 +75,6 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
}
}
if len(c.AdditionalISOFiles) > 0 {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for idx := range c.AdditionalISOFiles {
cdrom := c.AdditionalISOFiles[idx].Device
if c.AdditionalISOFiles[idx].Unmount {
if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes[cdrom] = "none,media=cdrom"
} else {
changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom"
}
}
}
if len(changes) > 0 {
_, err := client.SetVmConfig(vmRef, changes)
if err != nil {

View File

@ -56,18 +56,15 @@ func TestTemplateFinalize(t *testing.T) {
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
expectedVMConfig: map[string]interface{}{
"name": "my-template",
"description": "some-description",
"ide2": "none,media=cdrom",
},
expectedAction: multistep.ActionContinue,
},
@ -76,13 +73,11 @@ func TestTemplateFinalize(t *testing.T) {
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
CloudInit: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
"bootdisk": "virtio0",
"virtio0": "ceph01:base-223-disk-0,cache=unsafe,media=disk,size=32G",
},
@ -90,7 +85,6 @@ func TestTemplateFinalize(t *testing.T) {
expectedVMConfig: map[string]interface{}{
"name": "my-template",
"description": "some-description",
"ide2": "none,media=cdrom",
"ide3": "ceph01:cloudinit",
},
expectedAction: multistep.ActionContinue,
@ -100,7 +94,6 @@ func TestTemplateFinalize(t *testing.T) {
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: false,
CloudInit: true,
},
initialVMConfig: map[string]interface{}{
@ -116,27 +109,12 @@ func TestTemplateFinalize(t *testing.T) {
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "no cd-drive with unmount=true should returns halt",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide1": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "GetVmConfig error should return halt",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
CloudInit: true,
},
getConfigErr: fmt.Errorf("some error"),
expectCallSetConfig: false,
@ -147,12 +125,10 @@ func TestTemplateFinalize(t *testing.T) {
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
setConfigErr: fmt.Errorf("some error"),

View File

@ -15,7 +15,13 @@ import (
//
// It sets the vmRef state which is used throughout the later steps to reference the VM
// in API calls.
type stepStartVM struct{}
type stepStartVM struct {
vmCreator ProxmoxVMCreator
}
type ProxmoxVMCreator interface {
Create(*proxmox.VmRef, proxmox.ConfigQemu, multistep.StateBag) error
}
func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
@ -32,8 +38,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
kvm = false
}
isoFile := state.Get("iso_file").(string)
ui.Say("Creating VM")
config := proxmox.ConfigQemu{
Name: c.VMName,
@ -47,7 +51,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
QemuSockets: c.Sockets,
QemuOs: c.OS,
QemuVga: generateProxmoxVga(c.VGA),
QemuIso: isoFile,
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
QemuDisks: generateProxmoxDisks(c.Disks),
Scsihw: c.SCSIController,
@ -78,8 +81,9 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
vmRef.SetPool(c.Pool)
}
err := config.CreateVm(vmRef, client)
err := s.vmCreator.Create(vmRef, config, state)
if err != nil {
err := fmt.Errorf("Error creating VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@ -91,19 +95,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", vmRef)
for idx := range c.AdditionalISOFiles {
params := map[string]interface{}{
c.AdditionalISOFiles[idx].Device: c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom",
}
_, err = client.SetVmConfig(vmRef, params)
if err != nil {
err := fmt.Errorf("Error configuring VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Starting VM")
_, err = client.StartVm(vmRef)
if err != nil {

View File

@ -136,7 +136,7 @@ func TestTypeBootCommand(t *testing.T) {
step := stepTypeBootCommand{
c.builderConfig.BootConfig,
c.builderConfig.ctx,
c.builderConfig.Ctx,
}
action := step.Run(context.TODO(), state)
step.Cleanup(state)

View File

@ -0,0 +1,76 @@
package proxmoxiso
import (
"context"
proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/hcl/v2/hcldec"
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// The unique id for the builder
const BuilderID = "proxmox.iso"
type Builder struct {
config Config
}
// Builder implements packer.Builder
var _ packer.Builder = &Builder{}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return b.config.Prepare(raws...)
}
const downloadPathKey = "downloaded_iso_path"
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("iso-config", &b.config)
preSteps := []multistep.Step{
&common.StepDownload{
Checksum: b.config.ISOChecksum,
Description: "ISO",
Extension: b.config.TargetExtension,
ResultKey: downloadPathKey,
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
},
}
for idx := range b.config.AdditionalISOFiles {
preSteps = append(preSteps, &common.StepDownload{
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
Description: "additional ISO",
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
ResultKey: b.config.AdditionalISOFiles[idx].DownloadPathKey,
TargetPath: b.config.AdditionalISOFiles[idx].DownloadPathKey,
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
})
}
preSteps = append(preSteps,
&stepUploadISO{},
&stepUploadAdditionalISOs{},
)
postSteps := []multistep.Step{
&stepFinalizeISOTemplate{},
}
sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps, &isoVMCreator{})
return sb.Run(ctx, ui, hook, state)
}
type isoVMCreator struct{}
func (*isoVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error {
isoFile := state.Get("iso_file").(string)
config.QemuIso = isoFile
client := state.Get("proxmoxClient").(*proxmoxapi.Client)
return config.CreateVm(vmRef, client)
}

View File

@ -0,0 +1,114 @@
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig
package proxmoxiso
import (
"errors"
"fmt"
"log"
"strconv"
"strings"
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/packer"
)
type Config struct {
proxmox.Config `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
UnmountISO bool `mapstructure:"unmount_iso"`
shouldUploadISO bool
}
func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) {
var errs *packer.MultiError
_, warnings, merrs := c.Config.Prepare(c, raws...)
if merrs != nil {
errs = packer.MultiErrorAppend(errs, merrs)
}
// Check ISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.ISOFile != "" {
c.shouldUploadISO = false
} else {
isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.Ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.shouldUploadISO = true
}
if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified"))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
for idx := range c.AdditionalISOFiles {
// Check AdditionalISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.AdditionalISOFiles[idx].ISOFile != "" {
c.AdditionalISOFiles[idx].ShouldUploadISO = false
} else {
c.AdditionalISOFiles[idx].DownloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx)
isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.Ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.AdditionalISOFiles[idx].ShouldUploadISO = true
}
if c.AdditionalISOFiles[idx].Device == "" {
log.Printf("AdditionalISOFile %d Device not set, using default 'ide3'", idx)
c.AdditionalISOFiles[idx].Device = "ide3"
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "ide") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[3:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:]))
}
if busnumber == 2 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO"))
}
if busnumber > 3 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "sata") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 5 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "scsi") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 30 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30"))
}
}
if (c.AdditionalISOFiles[idx].ISOFile == "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0) || (c.AdditionalISOFiles[idx].ISOFile != "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
return nil, warnings, nil
}

View File

@ -0,0 +1,223 @@
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT.
package proxmoxiso
import (
"github.com/hashicorp/hcl/v2/hcldec"
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []proxmox.FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []proxmox.FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"proxmox_url": &hcldec.AttrSpec{Name: "proxmox_url", Type: cty.String, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"vm_id": &hcldec.AttrSpec{Name: "vm_id", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
"cpu_type": &hcldec.AttrSpec{Name: "cpu_type", Type: cty.String, Required: false},
"sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false},
"os": &hcldec.AttrSpec{Name: "os", Type: cty.String, Required: false},
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())},
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatnicConfig)(nil).HCL2Spec())},
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())},
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatstorageConfig)(nil).HCL2Spec())},
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,55 +1,17 @@
package proxmox
package proxmoxiso
import (
"strings"
"testing"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template"
)
func mandatoryConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"username": "apiuser@pve",
"password": "supersecret",
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
"node": "my-proxmox",
"ssh_username": "root",
}
}
func TestRequiredParameters(t *testing.T) {
var c Config
_, err := c.Prepare(make(map[string]interface{}))
if err == nil {
t.Fatal("Expected empty configuration to fail")
}
errs, ok := err.(*packer.MultiError)
if !ok {
t.Fatal("Expected errors to be packer.MultiError")
}
required := []string{"username", "password", "proxmox_url", "iso_file", "node", "ssh_username"}
for _, param := range required {
found := false
for _, err := range errs.Errors {
if strings.Contains(err.Error(), param) {
found = true
break
}
}
if !found {
t.Errorf("Expected error about missing parameter %q", required)
}
}
}
func TestBasicExampleFromDocsIsValid(t *testing.T) {
const config = `{
"builders": [
{
"type": "proxmox",
"type": "proxmox-iso",
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"insecure_skip_tls_verify": true,
"username": "apiuser@pve",
@ -93,9 +55,9 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) {
}
b := &Builder{}
_, warn, err := b.Prepare(tpl.Builders["proxmox"].Config)
_, _, err = b.Prepare(tpl.Builders["proxmox-iso"].Config)
if err != nil {
t.Fatal(err, warn)
t.Fatal(err)
}
// The example config does not set a number of optional fields. Validate that:
@ -154,7 +116,7 @@ func TestAgentSetToFalse(t *testing.T) {
cfg["qemu_agent"] = false
var c Config
warn, err := c.Prepare(cfg)
_, warn, err := c.Prepare(cfg)
if err != nil {
t.Fatal(err, warn)
}
@ -197,7 +159,7 @@ func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
cfg["network_adapters"] = devices
var c Config
_, err := c.Prepare(cfg)
_, _, err := c.Prepare(cfg)
if tt.expectedToFail == true && err == nil {
t.Error("expected config preparation to fail, but no error occured")
@ -246,7 +208,7 @@ func TestHardDiskControllerIOThreadSupport(t *testing.T) {
cfg["scsi_controller"] = tt.controller
var c Config
_, err := c.Prepare(cfg)
_, _, err := c.Prepare(cfg)
if tt.expectedToFail == true && err == nil {
t.Error("expected config preparation to fail, but no error occured")
@ -257,3 +219,14 @@ func TestHardDiskControllerIOThreadSupport(t *testing.T) {
}
}
}
func mandatoryConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"username": "apiuser@pve",
"password": "supersecret",
"node": "my-proxmox",
"ssh_username": "root",
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
}
}

View File

@ -0,0 +1,87 @@
package proxmoxiso
import (
"context"
"fmt"
"strings"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepFinalizeISOTemplate does any ISO-builder specific modifications after
// conversion to a template, and after the non-specific modifications in
// common.stepFinalizeTemplateConfig
type stepFinalizeISOTemplate struct{}
type templateFinalizer interface {
GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
}
var _ templateFinalizer = &proxmox.Client{}
func (s *stepFinalizeISOTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(templateFinalizer)
c := state.Get("iso-config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
changes := make(map[string]interface{})
if c.UnmountISO {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if vmParams["ide2"] == nil || !strings.HasSuffix(vmParams["ide2"].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, ide2 is not present, or not a cdrom media")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes["ide2"] = "none,media=cdrom"
}
if len(c.AdditionalISOFiles) > 0 {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for idx := range c.AdditionalISOFiles {
cdrom := c.AdditionalISOFiles[idx].Device
if c.AdditionalISOFiles[idx].Unmount {
if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes[cdrom] = "none,media=cdrom"
} else {
changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom"
}
}
}
if len(changes) > 0 {
_, err := client.SetVmConfig(vmRef, changes)
if err != nil {
err := fmt.Errorf("Error updating template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepFinalizeISOTemplate) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,131 @@
package proxmoxiso
import (
"context"
"fmt"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type finalizerMock struct {
getConfig func() (map[string]interface{}, error)
setConfig func(map[string]interface{}) (string, error)
}
func (m finalizerMock) GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error) {
return m.getConfig()
}
func (m finalizerMock) SetVmConfig(vmref *proxmox.VmRef, c map[string]interface{}) (interface{}, error) {
return m.setConfig(c)
}
var _ templateFinalizer = finalizerMock{}
func TestISOTemplateFinalize(t *testing.T) {
cs := []struct {
name string
builderConfig *Config
initialVMConfig map[string]interface{}
getConfigErr error
expectCallSetConfig bool
expectedVMConfig map[string]interface{}
setConfigErr error
expectedAction multistep.StepAction
}{
{
name: "default config does nothing",
builderConfig: &Config{},
initialVMConfig: map[string]interface{}{
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: false,
expectedVMConfig: map[string]interface{}{
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectedAction: multistep.ActionContinue,
},
{
name: "should unmount when configured",
builderConfig: &Config{
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
expectedVMConfig: map[string]interface{}{
"ide2": "none,media=cdrom",
},
expectedAction: multistep.ActionContinue,
},
{
name: "no cd-drive with unmount=true should returns halt",
builderConfig: &Config{
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"ide1": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "GetVmConfig error should return halt",
builderConfig: &Config{
UnmountISO: true,
},
getConfigErr: fmt.Errorf("some error"),
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "SetVmConfig error should return halt",
builderConfig: &Config{
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
setConfigErr: fmt.Errorf("some error"),
expectedAction: multistep.ActionHalt,
},
}
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
finalizer := finalizerMock{
getConfig: func() (map[string]interface{}, error) {
return c.initialVMConfig, c.getConfigErr
},
setConfig: func(cfg map[string]interface{}) (string, error) {
if !c.expectCallSetConfig {
t.Error("Did not expect SetVmConfig to be called")
}
for key, val := range c.expectedVMConfig {
if cfg[key] != val {
t.Errorf("Expected %q to be %q, got %q", key, val, cfg[key])
}
}
return "", c.setConfigErr
},
}
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("iso-config", c.builderConfig)
state.Put("vmRef", proxmox.NewVmRef(1))
state.Put("proxmoxClient", finalizer)
step := stepFinalizeISOTemplate{}
action := step.Run(context.TODO(), state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
}
})
}
}

View File

@ -1,9 +1,8 @@
package proxmox
package proxmoxiso
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
@ -16,24 +15,20 @@ import (
// to the VM
type stepUploadAdditionalISOs struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(uploader)
c := state.Get("config").(*Config)
c := state.Get("iso-config").(*Config)
for idx := range c.AdditionalISOFiles {
if !c.AdditionalISOFiles[idx].shouldUploadISO {
if !c.AdditionalISOFiles[idx].ShouldUploadISO {
state.Put("additional_iso_files", c.AdditionalISOFiles)
continue
}
p := state.Get(c.AdditionalISOFiles[idx].downloadPathKey).(string)
p := state.Get(c.AdditionalISOFiles[idx].DownloadPathKey).(string)
if p == "" {
err := fmt.Errorf("Path to downloaded ISO was empty")
state.Put("erroe", err)

View File

@ -1,8 +1,9 @@
package proxmox
package proxmoxiso
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
@ -14,12 +15,16 @@ import (
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
type stepUploadISO struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(uploader)
c := state.Get("config").(*Config)
c := state.Get("iso-config").(*Config)
if !c.shouldUploadISO {
state.Put("iso_file", c.ISOFile)
@ -53,6 +58,7 @@ func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multi
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
state.Put("iso_file", isoStoragePath)
return multistep.ActionContinue
}

View File

@ -1,4 +1,4 @@
package proxmox
package proxmoxiso
import (
"context"
@ -106,7 +106,7 @@ func TestUploadISO(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("config", c.builderConfig)
state.Put("iso-config", c.builderConfig)
state.Put(downloadPathKey, c.downloadPath)
state.Put("proxmoxClient", m)

View File

@ -49,6 +49,8 @@ import (
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
proxmoxbuilder "github.com/hashicorp/packer/builder/proxmox"
proxmoxclonebuilder "github.com/hashicorp/packer/builder/proxmox/clone"
proxmoxisobuilder "github.com/hashicorp/packer/builder/proxmox/iso"
qemubuilder "github.com/hashicorp/packer/builder/qemu"
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
@ -146,6 +148,8 @@ var Builders = map[string]packer.Builder{
"parallels-pvm": new(parallelspvmbuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder),
"proxmox": new(proxmoxbuilder.Builder),
"proxmox-clone": new(proxmoxclonebuilder.Builder),
"proxmox-iso": new(proxmoxisobuilder.Builder),
"qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),

View File

@ -59,6 +59,7 @@ func init() {
"iso-checksum-type-and-url": new(FixerISOChecksumTypeAndURL),
"qemu-host-port": new(FixerQEMUHostPort),
"azure-exclude_from_latest": new(FixerAzureExcludeFromLatest),
"proxmox-type": new(FixerProxmoxType),
}
FixerOrder = []string{
@ -95,5 +96,6 @@ func init() {
"iso-checksum-type-and-url",
"qemu-host-port",
"azure-exclude_from_latest",
"proxmox-type",
}
}

49
fix/fixer_proxmox_type.go Normal file
View File

@ -0,0 +1,49 @@
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerProxmoxType updates proxmox builder types to proxmox-iso
type FixerProxmoxType struct{}
func (FixerProxmoxType) DeprecatedOptions() []string {
return []string{}
}
func (FixerProxmoxType) Fix(input map[string]interface{}) (map[string]interface{}, error) {
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
for _, builder := range tpl.Builders {
builderTypeRaw, ok := builder["type"]
if !ok {
continue
}
builderType, ok := builderTypeRaw.(string)
if !ok {
continue
}
if builderType != "proxmox" {
continue
}
builder["type"] = "proxmox-iso"
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerProxmoxType) Synopsis() string {
return `Updates the builder type proxmox to proxmox-iso`
}

View File

@ -0,0 +1,73 @@
package fix
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFixerProxmoxType_Impl(t *testing.T) {
var raw interface{}
raw = new(FixerProxmoxType)
if _, ok := raw.(Fixer); !ok {
t.Fatalf("must be a Fixer")
}
}
func TestFixerProxmoxType_Fix(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
{
Input: map[string]interface{}{
"type": "proxmox",
},
Expected: map[string]interface{}{
"type": "proxmox-iso",
},
},
{
Input: map[string]interface{}{
"type": "proxmox-iso",
},
Expected: map[string]interface{}{
"type": "proxmox-iso",
},
},
{
Input: map[string]interface{}{
"type": "proxmox-clone",
},
Expected: map[string]interface{}{
"type": "proxmox-clone",
},
},
}
for _, tc := range cases {
var f FixerProxmoxType
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
assert.Equal(t, expected, output, "Should be equal")
}
}

View File

@ -136,15 +136,17 @@ func listDirectories(path string) ([]string, error) {
for _, item := range items {
// We only want directories
if item.IsDir() {
currentDir := filepath.Join(path, item.Name())
names = append(names, currentDir)
if !item.IsDir() ||
item.Name() == "common" {
continue
}
currentDir := filepath.Join(path, item.Name())
names = append(names, currentDir)
// Do some recursion
subNames, err := listDirectories(currentDir)
if err == nil {
names = append(names, subNames...)
}
// Do some recursion
subNames, err := listDirectories(currentDir)
if err == nil {
names = append(names, subNames...)
}
}

View File

@ -217,7 +217,7 @@ export default [
},
{ category: 'parallels', content: ['iso', 'pvm'] },
'profitbricks',
'proxmox',
{ category: 'proxmox', content: ['iso', 'clone'] },
'qemu',
'scaleway',
'tencentcloud-cvm',

View File

@ -0,0 +1,234 @@
---
description: |
The proxmox image Packer builder is able to create new images for use with
Proxmox VE. The builder takes a cloud-init enabled virtual machine
template name, runs any provisioning necessary on the image after
launching it, then creates a virtual machine template.
layout: docs
page_title: Proxmox Clone - Builders
sidebar_title: Clone
---
# Proxmox Builder (from an image)
Type: `proxmox-clone`
The `proxmox-clone` Packer builder is able to create new images for use with
[Proxmox](https://www.proxmox.com/en/proxmox-ve). The builder takes a virtual
machine template, runs any provisioning necessary on the image after launching it,
then creates a virtual machine template. This template can then be used as to
create new virtual machines within Proxmox.
The builder does _not_ manage templates. Once it creates a template, it is up
to you to use it or delete it.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](/docs/templates/communicator) can be configured for this
builder.
If no communicator is defined, an SSH key is generated for use, and is used
in the image's Cloud-Init settings for provisioning.
### Required:
- `proxmox_url` (string) - URL to the Proxmox API, including the full path,
so `https://<server>:<port>/api2/json` for example.
Can also be set via the `PROXMOX_URL` environment variable.
- `username` (string) - Username when authenticating to Proxmox, including
the realm. For example `user@pve` to use the local Proxmox realm.
Can also be set via the `PROXMOX_USERNAME` environment variable.
- `password` (string) - Password for the user.
Can also be set via the `PROXMOX_PASSWORD` environment variable.
- `node` (string) - Which node in the Proxmox cluster to start the virtual
machine on during creation.
- `clone_vm` (string) - The name of the VM packer should clone and build from.
### Optional:
- `insecure_skip_tls_verify` (bool) - Skip validating the certificate.
- `pool` (string) - Name of resource pool to create virtual machine in.
- `vm_name` (string) - Name of the virtual machine during creation. If not
given, a random uuid will be used.
- `vm_id` (int) - The ID used to reference the virtual machine. This will
also be the ID of the final template. If not given, the next free ID on
the node will be used.
- `memory` (int) - How much memory, in megabytes, to give the virtual
machine. Defaults to `512`.
- `cores` (int) - How many CPU cores to give the virtual machine. Defaults
to `1`.
- `sockets` (int) - How many CPU sockets to give the virtual machine.
Defaults to `1`
- `cpu_type` (string) - The CPU type to emulate. See the Proxmox API
documentation for the complete list of accepted values. For best
performance, set this to `host`. Defaults to `kvm64`.
- `os` (string) - The operating system. Can be `wxp`, `w2k`, `w2k3`, `w2k8`,
`wvista`, `win7`, `win8`, `win10`, `l24` (Linux 2.4), `l26` (Linux 2.6+),
`solaris` or `other`. Defaults to `other`.
- `vga` (object) - The graphics adapter to use. Example:
```json
{
"type": "vmware",
"memory": 32
}
```
- `type` (string) - Can be `cirrus`, `none`, `qxl`,`qxl2`, `qxl3`,
`qxl4`, `serial0`, `serial1`, `serial2`, `serial3`, `std`, `virtio`, `vmware`.
Defaults to `std`.
- `memory` (int) - How much memory to assign.
- `network_adapters` (array of objects) - Network adapters attached to the
virtual machine. Example:
```json
[
{
"model": "virtio",
"bridge": "vmbr0",
"vlan_tag": "10",
"firewall": true
}
]
```
- `bridge` (string) - Required. Which Proxmox bridge to attach the
adapter to.
- `model` (string) - Model of the virtual network adapter. Can be
`rtl8139`, `ne2k_pci`, `e1000`, `pcnet`, `virtio`, `ne2k_isa`,
`i82551`, `i82557b`, `i82559er`, `vmxnet3`, `e1000-82540em`,
`e1000-82544gc` or `e1000-82545em`. Defaults to `e1000`.
- `mac_address` (string) - Give the adapter a specific MAC address. If
not set, defaults to a random MAC.
- `vlan_tag` (string) - If the adapter should tag packets. Defaults to
no tagging.
- `firewall` (bool) - If the interface should be protected by the firewall.
Defaults to `false`.
- `packet_queues` (int) - Number of packet queues to be used on the device.
Values greater than 1 indicate that the multiqueue feature is activated.
For best performance, set this to the number of cores available to the
virtual machine. CPU load on the host and guest systems will increase as
the traffic increases, so activate this option only when the VM has to
handle a great number of incoming connections, such as when the VM is
operating as a router, reverse proxy or a busy HTTP server. Requires
`virtio` network adapter. Defaults to `0`.
- `disks` (array of objects) - Disks attached to the virtual machine.
Example:
```json
[
{
"type": "scsi",
"disk_size": "5G",
"storage_pool": "local-lvm",
"storage_pool_type": "lvm"
}
]
```
- `storage_pool` (string) - Required. Name of the Proxmox storage pool
to store the virtual machine disk on. A `local-lvm` pool is allocated
by the installer, for example.
- `storage_pool_type` (string) - Required. The type of the pool, can
be `lvm`, `lvm-thin`, `zfspool`, `cephfs`, `rbd` or `directory`.
- `type` (string) - The type of disk. Can be `scsi`, `sata`, `virtio` or
`ide`. Defaults to `scsi`.
- `disk_size` (string) - The size of the disk, including a unit suffix, such
as `10G` to indicate 10 gigabytes.
- `cache_mode` (string) - How to cache operations to the disk. Can be
`none`, `writethrough`, `writeback`, `unsafe` or `directsync`.
Defaults to `none`.
- `format` (string) - The format of the file backing the disk. Can be
`raw`, `cow`, `qcow`, `qed`, `qcow2`, `vmdk` or `cloop`. Defaults to
`raw`.
- `template_name` (string) - Name of the template. Defaults to the generated
name used during creation.
- `template_description` (string) - Description of the template, visible in
the Proxmox interface.
- `onboot` (boolean) - Specifies whether a VM will be started during system
bootup. Defaults to `false`.
- `disable_kvm` (boolean) - Disables KVM hardware virtualization. Defaults to `false`.
- `scsi_controller` (string) - The SCSI controller model to emulate. Can be `lsi`,
`lsi53c810`, `virtio-scsi-pci`, `virtio-scsi-single`, `megasas`, or `pvscsi`.
Defaults to `lsi`.
- `full_clone` (bool) - Whether to run a full or shallow clone from the base clone_vm. Defaults to `true`.
## Example: Cloud-Init enabled Debian
Here is a basic example creating a Debian 10 server image. This assumes
that there exists a Cloud-Init enabled image on the Proxmox server named
`debian-10-4`.
```json
{
"variables": {
"proxmox_url": "{{env `PROXMOX_URL`}}",
"proxmox_username": "{{env `PROXMOX_USERNAME`}}",
"proxmox_password": "{{env `PROXMOX_PASSWORD`}}"
},
"builders": [
{
"type": "proxmox-clone",
"proxmox_url": "{{user `proxmox_url`}}",
"username": "{{user `proxmox_username`}}",
"password": "{{user `proxmox_password`}}",
"node": "pve",
"insecure_skip_tls_verify": true,
"clone_vm": "debian-10-4",
"template_name": "debian-scaffolding",
"template_description": "image made from cloud-init image",
"pool": "api-users",
"os": "l26",
"cores": 1,
"sockets": 1,
"memory": 2048,
"network_adapters": [
{
"bridge": "vmbr0"
}
]
}
],
"description": "A template for building a base"
}
```

View File

@ -0,0 +1,29 @@
---
description: >
The Proxmox Packer builder is able to create Cloud-Init
virtual machine images on a Proxmox server.
layout: docs
page_title: Proxmox - Builders
sidebar_title: Proxmox
---
# Proxmox Builder
The Proxmox Packer builder is able to create
[Proxmox](https://www.proxmox.com/en/proxmox-ve) virtual
machines and store them as new Proxmox Virutal Machine images.
Packer is able to target both ISO and existing Cloud-Init images:
- [proxmox-clone](/docs/builders/proxmox-clone) - The proxmox image
Packer builder is able to create new images for use with
Proxmox VE. The builder takes a cloud-init enabled virtual machine
template name, runs any provisioning necessary on the image after
launching it, then creates a virtual machine template.
- [proxmox-iso](/docs/builders/proxmox-iso) - The proxmox Packer
builder is able to create new images for use with
Proxmox VE. The builder takes an ISO source, runs any provisioning
necessary on the image after launching it, then creates a virtual machine
template.

View File

@ -5,15 +5,15 @@ description: |
necessary on the image after launching it, then creates a virtual machine
template.
layout: docs
page_title: Proxmox - Builders
sidebar_title: Proxmox
page_title: Proxmox ISO - Builders
sidebar_title: ISO
---
# Proxmox Builder
# Proxmox Builder (from an ISO)
Type: `proxmox`
Type: `proxmox-iso`
The `proxmox` Packer builder is able to create new images for use with
The `proxmox-iso` Packer builder is able to create new images for use with
[Proxmox](https://www.proxmox.com/en/proxmox-ve). The builder takes an ISO
image, runs any provisioning necessary on the image after launching it, then
creates a virtual machine template. This template can then be used as to