diff --git a/builder/proxmox/clone/builder.go b/builder/proxmox/clone/builder.go new file mode 100644 index 000000000..c56db14d4 --- /dev/null +++ b/builder/proxmox/clone/builder.go @@ -0,0 +1,39 @@ +package proxmoxclone + +import ( + "context" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/proxmox/common" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// The unique id for the builder +const BuilderID = "proxmox.clone" + +type Builder struct { + config Config +} + +// 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) { + 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) + + steps := []multistep.Step{} + postSteps := []multistep.Step{} + + sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, steps, postSteps) + return sb.Run(ctx, ui, hook, state) +} diff --git a/builder/proxmox/clone/config.go b/builder/proxmox/clone/config.go new file mode 100644 index 000000000..bede7db14 --- /dev/null +++ b/builder/proxmox/clone/config.go @@ -0,0 +1,27 @@ +//go:generate mapstructure-to-hcl2 -type Config + +package proxmoxclone + +import ( + "github.com/hashicorp/packer/builder/proxmox/common" + "github.com/hashicorp/packer/packer" +) + +type Config struct { + proxmox.Config `mapstructure:",squash"` + + CloneVM string `mapstructure:"clone_vm"` +} + +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 +} diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go new file mode 100644 index 000000000..cc5a87d09 --- /dev/null +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -0,0 +1,219 @@ +// 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"` + 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"` + Data interface{} `cty:"data" hcl:"data"` + Funcs map[string]interface{} `cty:"funcs" hcl:"funcs"` + UserVariables map[string]string `cty:"user_variables" hcl:"user_variables"` + SensitiveVariables []string `cty:"sensitive_variables" hcl:"sensitive_variables"` + EnableEnv *bool `cty:"enable_env" hcl:"enable_env"` + BuildName *string `cty:"build_name" hcl:"build_name"` + BuildType *string `cty:"build_type" hcl:"build_type"` + TemplatePath *string `cty:"template_path" hcl:"template_path"` + CloneVM *string `mapstructure:"clone_vm" cty:"clone_vm" hcl:"clone_vm"` +} + +// 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}, + "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}, + "data": &hcldec.AttrSpec{Name: "data", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "funcs": &hcldec.AttrSpec{Name: "funcs", Type: cty.Map(cty.String), Required: false}, + "user_variables": &hcldec.AttrSpec{Name: "user_variables", Type: cty.Map(cty.String), Required: false}, + "sensitive_variables": &hcldec.AttrSpec{Name: "sensitive_variables", Type: cty.List(cty.String), Required: false}, + "enable_env": &hcldec.AttrSpec{Name: "enable_env", Type: cty.Bool, Required: false}, + "build_name": &hcldec.AttrSpec{Name: "build_name", Type: cty.String, Required: false}, + "build_type": &hcldec.AttrSpec{Name: "build_type", Type: cty.String, Required: false}, + "template_path": &hcldec.AttrSpec{Name: "template_path", Type: cty.String, Required: false}, + "clone_vm": &hcldec.AttrSpec{Name: "clone_vm", Type: cty.String, Required: false}, + } + return s +} diff --git a/builder/proxmox/artifact.go b/builder/proxmox/common/artifact.go similarity index 91% rename from builder/proxmox/artifact.go rename to builder/proxmox/common/artifact.go index 8b11e266d..059d8ed81 100644 --- a/builder/proxmox/artifact.go +++ b/builder/proxmox/common/artifact.go @@ -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 { diff --git a/builder/proxmox/bootcommand_driver.go b/builder/proxmox/common/bootcommand_driver.go similarity index 100% rename from builder/proxmox/bootcommand_driver.go rename to builder/proxmox/common/bootcommand_driver.go diff --git a/builder/proxmox/builder.go b/builder/proxmox/common/builder.go similarity index 70% rename from builder/proxmox/builder.go rename to builder/proxmox/common/builder.go index b2387e1c0..b6742b1ee 100644 --- a/builder/proxmox/builder.go +++ b/builder/proxmox/common/builder.go @@ -7,40 +7,31 @@ import ( "fmt" "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" +func NewSharedBuilder(id string, config Config, preSteps []multistep.Step, postSteps []multistep.Step) *Builder { + return &Builder{ + id: id, + config: config, + preSteps: preSteps, + postSteps: postSteps, + } +} type Builder struct { + id string config Config + preSteps []multistep.Step + postSteps []multistep.Step 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) { +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, @@ -56,36 +47,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } // 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{}, + coreSteps := []multistep.Step{ &stepStartVM{}, &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, @@ -95,7 +63,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &stepTypeBootCommand{ BootConfig: b.config.BootConfig, - Ctx: b.config.ctx, + Ctx: b.config.Ctx, }, &communicator.StepConnect{ Config: &b.config.Comm, @@ -109,8 +77,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack &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) @@ -130,6 +99,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } artifact := &Artifact{ + builderID: b.id, templateID: tplID, proxmoxClient: b.proxmoxClient, StateData: map[string]interface{}{"generated_data": state.Get("generated_data")}, diff --git a/builder/proxmox/config.go b/builder/proxmox/common/config.go similarity index 60% rename from builder/proxmox/config.go rename to builder/proxmox/common/config.go index f888a3c4e..ceeb19a1f 100644 --- a/builder/proxmox/config.go +++ b/builder/proxmox/common/config.go @@ -1,4 +1,4 @@ -//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig +//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig package proxmox @@ -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,16 +54,11 @@ 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"` - - ctx interpolate.Context + Ctx interpolate.Context `mapstructure:",squash",mapstructure-to-hcl2:"skip"` } type nicConfig struct { @@ -90,27 +81,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", @@ -118,11 +100,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 == "" { @@ -195,89 +179,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 == "" { @@ -316,11 +225,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 { diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go new file mode 100644 index 000000000..973eb7158 --- /dev/null +++ b/builder/proxmox/common/config.hcl2spec.go @@ -0,0 +1,307 @@ +// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT. +package proxmox + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "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"` + 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 *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"` + 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"` + Data interface{} `cty:"data" hcl:"data"` + Funcs map[string]interface{} `cty:"funcs" hcl:"funcs"` + UserVariables map[string]string `cty:"user_variables" hcl:"user_variables"` + SensitiveVariables []string `cty:"sensitive_variables" hcl:"sensitive_variables"` + EnableEnv *bool `cty:"enable_env" hcl:"enable_env"` + BuildName *string `cty:"build_name" hcl:"build_name"` + BuildType *string `cty:"build_type" hcl:"build_type"` + TemplatePath *string `cty:"template_path" hcl:"template_path"` +} + +// 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}, + "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((*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())}, + "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}, + "data": &hcldec.AttrSpec{Name: "data", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "funcs": &hcldec.AttrSpec{Name: "funcs", Type: cty.Map(cty.String), Required: false}, + "user_variables": &hcldec.AttrSpec{Name: "user_variables", Type: cty.Map(cty.String), Required: false}, + "sensitive_variables": &hcldec.AttrSpec{Name: "sensitive_variables", Type: cty.List(cty.String), Required: false}, + "enable_env": &hcldec.AttrSpec{Name: "enable_env", Type: cty.Bool, Required: false}, + "build_name": &hcldec.AttrSpec{Name: "build_name", Type: cty.String, Required: false}, + "build_type": &hcldec.AttrSpec{Name: "build_type", Type: cty.String, Required: false}, + "template_path": &hcldec.AttrSpec{Name: "template_path", Type: cty.String, Required: false}, + } + return s +} + +// FlatdiskConfig is an auto-generated flat version of diskConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatdiskConfig struct { + Type *string `mapstructure:"type" cty:"type" hcl:"type"` + StoragePool *string `mapstructure:"storage_pool" cty:"storage_pool" hcl:"storage_pool"` + StoragePoolType *string `mapstructure:"storage_pool_type" cty:"storage_pool_type" hcl:"storage_pool_type"` + Size *string `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"` + CacheMode *string `mapstructure:"cache_mode" cty:"cache_mode" hcl:"cache_mode"` + DiskFormat *string `mapstructure:"format" cty:"format" hcl:"format"` +} + +// FlatMapstructure returns a new FlatdiskConfig. +// FlatdiskConfig is an auto-generated flat version of diskConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*diskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatdiskConfig) +} + +// HCL2Spec returns the hcl spec of a diskConfig. +// This spec is used by HCL to read the fields of diskConfig. +// The decoded values from this spec will then be applied to a FlatdiskConfig. +func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, + "storage_pool": &hcldec.AttrSpec{Name: "storage_pool", Type: cty.String, Required: false}, + "storage_pool_type": &hcldec.AttrSpec{Name: "storage_pool_type", Type: cty.String, Required: false}, + "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false}, + "cache_mode": &hcldec.AttrSpec{Name: "cache_mode", Type: cty.String, Required: false}, + "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, + } + return s +} + +// FlatnicConfig is an auto-generated flat version of nicConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatnicConfig struct { + Model *string `mapstructure:"model" cty:"model" hcl:"model"` + PacketQueues *int `mapstructure:"packet_queues" cty:"packet_queues" hcl:"packet_queues"` + MACAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"` + Bridge *string `mapstructure:"bridge" cty:"bridge" hcl:"bridge"` + VLANTag *string `mapstructure:"vlan_tag" cty:"vlan_tag" hcl:"vlan_tag"` + Firewall *bool `mapstructure:"firewall" cty:"firewall" hcl:"firewall"` +} + +// FlatMapstructure returns a new FlatnicConfig. +// FlatnicConfig is an auto-generated flat version of nicConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*nicConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatnicConfig) +} + +// HCL2Spec returns the hcl spec of a nicConfig. +// This spec is used by HCL to read the fields of nicConfig. +// The decoded values from this spec will then be applied to a FlatnicConfig. +func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "model": &hcldec.AttrSpec{Name: "model", Type: cty.String, Required: false}, + "packet_queues": &hcldec.AttrSpec{Name: "packet_queues", Type: cty.Number, Required: false}, + "mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false}, + "bridge": &hcldec.AttrSpec{Name: "bridge", Type: cty.String, Required: false}, + "vlan_tag": &hcldec.AttrSpec{Name: "vlan_tag", Type: cty.String, Required: false}, + "firewall": &hcldec.AttrSpec{Name: "firewall", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatvgaConfig is an auto-generated flat version of vgaConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatvgaConfig struct { + Type *string `mapstructure:"type" cty:"type" hcl:"type"` + Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` +} + +// FlatMapstructure returns a new FlatvgaConfig. +// FlatvgaConfig is an auto-generated flat version of vgaConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*vgaConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatvgaConfig) +} + +// HCL2Spec returns the hcl spec of a vgaConfig. +// This spec is used by HCL to read the fields of vgaConfig. +// The decoded values from this spec will then be applied to a FlatvgaConfig. +func (*FlatvgaConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, + "memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false}, + } + return s +} diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go new file mode 100644 index 000000000..b68c6fcc8 --- /dev/null +++ b/builder/proxmox/common/config_test.go @@ -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()) + } + } +} diff --git a/builder/proxmox/step_convert_to_template.go b/builder/proxmox/common/step_convert_to_template.go similarity index 100% rename from builder/proxmox/step_convert_to_template.go rename to builder/proxmox/common/step_convert_to_template.go diff --git a/builder/proxmox/step_convert_to_template_test.go b/builder/proxmox/common/step_convert_to_template_test.go similarity index 100% rename from builder/proxmox/step_convert_to_template_test.go rename to builder/proxmox/common/step_convert_to_template_test.go diff --git a/builder/proxmox/step_finalize_template_config.go b/builder/proxmox/common/step_finalize_template_config.go similarity index 66% rename from builder/proxmox/step_finalize_template_config.go rename to builder/proxmox/common/step_finalize_template_config.go index 570c77b0b..0aebb9aca 100644 --- a/builder/proxmox/step_finalize_template_config.go +++ b/builder/proxmox/common/step_finalize_template_config.go @@ -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 { diff --git a/builder/proxmox/step_finalize_template_config_test.go b/builder/proxmox/common/step_finalize_template_config_test.go similarity index 84% rename from builder/proxmox/step_finalize_template_config_test.go rename to builder/proxmox/common/step_finalize_template_config_test.go index e045cb0ce..646e6f73d 100644 --- a/builder/proxmox/step_finalize_template_config_test.go +++ b/builder/proxmox/common/step_finalize_template_config_test.go @@ -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"), diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/common/step_start_vm.go similarity index 91% rename from builder/proxmox/step_start_vm.go rename to builder/proxmox/common/step_start_vm.go index c128fe44c..b364b9a67 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -17,6 +17,10 @@ import ( // in API calls. type stepStartVM struct{} +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) client := state.Get("proxmoxClient").(*proxmox.Client) @@ -32,7 +36,7 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist kvm = false } - isoFile := state.Get("iso_file").(string) + vmStarter := state.Get("vm-creator").(ProxmoxVMCreator) ui.Say("Creating VM") config := proxmox.ConfigQemu{ @@ -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 := vmStarter.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 { diff --git a/builder/proxmox/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go similarity index 100% rename from builder/proxmox/step_start_vm_test.go rename to builder/proxmox/common/step_start_vm_test.go diff --git a/builder/proxmox/step_success.go b/builder/proxmox/common/step_success.go similarity index 100% rename from builder/proxmox/step_success.go rename to builder/proxmox/common/step_success.go diff --git a/builder/proxmox/step_type_boot_command.go b/builder/proxmox/common/step_type_boot_command.go similarity index 100% rename from builder/proxmox/step_type_boot_command.go rename to builder/proxmox/common/step_type_boot_command.go diff --git a/builder/proxmox/step_type_boot_command_test.go b/builder/proxmox/common/step_type_boot_command_test.go similarity index 99% rename from builder/proxmox/step_type_boot_command_test.go rename to builder/proxmox/common/step_type_boot_command_test.go index c1581a3f8..88034753a 100644 --- a/builder/proxmox/step_type_boot_command_test.go +++ b/builder/proxmox/common/step_type_boot_command_test.go @@ -108,7 +108,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) diff --git a/builder/proxmox/config.hcl2spec.go b/builder/proxmox/config.hcl2spec.go deleted file mode 100644 index ac804f32e..000000000 --- a/builder/proxmox/config.hcl2spec.go +++ /dev/null @@ -1,348 +0,0 @@ -// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; DO NOT EDIT. -package proxmox - -import ( - "github.com/hashicorp/hcl/v2/hcldec" - "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"` - 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"` - 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 *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"` -} - -// 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}, - "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}, - "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((*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())}, - } - return s -} - -// FlatdiskConfig is an auto-generated flat version of diskConfig. -// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. -type FlatdiskConfig struct { - Type *string `mapstructure:"type" cty:"type" hcl:"type"` - StoragePool *string `mapstructure:"storage_pool" cty:"storage_pool" hcl:"storage_pool"` - StoragePoolType *string `mapstructure:"storage_pool_type" cty:"storage_pool_type" hcl:"storage_pool_type"` - Size *string `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"` - CacheMode *string `mapstructure:"cache_mode" cty:"cache_mode" hcl:"cache_mode"` - DiskFormat *string `mapstructure:"format" cty:"format" hcl:"format"` -} - -// FlatMapstructure returns a new FlatdiskConfig. -// FlatdiskConfig is an auto-generated flat version of diskConfig. -// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*diskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { - return new(FlatdiskConfig) -} - -// HCL2Spec returns the hcl spec of a diskConfig. -// This spec is used by HCL to read the fields of diskConfig. -// The decoded values from this spec will then be applied to a FlatdiskConfig. -func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec { - s := map[string]hcldec.Spec{ - "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, - "storage_pool": &hcldec.AttrSpec{Name: "storage_pool", Type: cty.String, Required: false}, - "storage_pool_type": &hcldec.AttrSpec{Name: "storage_pool_type", Type: cty.String, Required: false}, - "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false}, - "cache_mode": &hcldec.AttrSpec{Name: "cache_mode", Type: cty.String, Required: false}, - "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, - } - return s -} - -// FlatnicConfig is an auto-generated flat version of nicConfig. -// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. -type FlatnicConfig struct { - Model *string `mapstructure:"model" cty:"model" hcl:"model"` - PacketQueues *int `mapstructure:"packet_queues" cty:"packet_queues" hcl:"packet_queues"` - MACAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"` - Bridge *string `mapstructure:"bridge" cty:"bridge" hcl:"bridge"` - VLANTag *string `mapstructure:"vlan_tag" cty:"vlan_tag" hcl:"vlan_tag"` - Firewall *bool `mapstructure:"firewall" cty:"firewall" hcl:"firewall"` -} - -// FlatMapstructure returns a new FlatnicConfig. -// FlatnicConfig is an auto-generated flat version of nicConfig. -// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*nicConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { - return new(FlatnicConfig) -} - -// HCL2Spec returns the hcl spec of a nicConfig. -// This spec is used by HCL to read the fields of nicConfig. -// The decoded values from this spec will then be applied to a FlatnicConfig. -func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec { - s := map[string]hcldec.Spec{ - "model": &hcldec.AttrSpec{Name: "model", Type: cty.String, Required: false}, - "packet_queues": &hcldec.AttrSpec{Name: "packet_queues", Type: cty.Number, Required: false}, - "mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false}, - "bridge": &hcldec.AttrSpec{Name: "bridge", Type: cty.String, Required: false}, - "vlan_tag": &hcldec.AttrSpec{Name: "vlan_tag", Type: cty.String, Required: false}, - "firewall": &hcldec.AttrSpec{Name: "firewall", Type: cty.Bool, Required: false}, - } - return s -} - -// FlatstorageConfig is an auto-generated flat version of storageConfig. -// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. -type FlatstorageConfig struct { - 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"` - Device *string `mapstructure:"device" cty:"device" hcl:"device"` - 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"` -} - -// FlatMapstructure returns a new FlatstorageConfig. -// FlatstorageConfig is an auto-generated flat version of storageConfig. -// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { - return new(FlatstorageConfig) -} - -// HCL2Spec returns the hcl spec of a storageConfig. -// This spec is used by HCL to read the fields of storageConfig. -// The decoded values from this spec will then be applied to a FlatstorageConfig. -func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec { - s := map[string]hcldec.Spec{ - "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}, - "device": &hcldec.AttrSpec{Name: "device", 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": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, - } - return s -} - -// FlatvgaConfig is an auto-generated flat version of vgaConfig. -// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. -type FlatvgaConfig struct { - Type *string `mapstructure:"type" cty:"type" hcl:"type"` - Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` -} - -// FlatMapstructure returns a new FlatvgaConfig. -// FlatvgaConfig is an auto-generated flat version of vgaConfig. -// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*vgaConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { - return new(FlatvgaConfig) -} - -// HCL2Spec returns the hcl spec of a vgaConfig. -// This spec is used by HCL to read the fields of vgaConfig. -// The decoded values from this spec will then be applied to a FlatvgaConfig. -func (*FlatvgaConfig) HCL2Spec() map[string]hcldec.Spec { - s := map[string]hcldec.Spec{ - "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, - "memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false}, - } - return s -} diff --git a/builder/proxmox/iso/builder.go b/builder/proxmox/iso/builder.go new file mode 100644 index 000000000..bffa0193c --- /dev/null +++ b/builder/proxmox/iso/builder.go @@ -0,0 +1,81 @@ +package proxmoxiso + +import ( + "context" + + proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/proxmox/common" + "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// The unique id for the builder +const BuilderID = "proxmox.clone" + +type Builder struct { + config Config +} + +// 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) { + 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) + state.Put("vm-creator", &isoVMCreator{}) + + 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) + return sb.Run(ctx, ui, hook, state) +} + +type isoVMCreator struct{} + +var _ proxmox.ProxmoxVMCreator = &isoVMCreator{} + +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) +} diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go new file mode 100644 index 000000000..9346a24a0 --- /dev/null +++ b/builder/proxmox/iso/config.go @@ -0,0 +1,125 @@ +//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig + +package proxmoxiso + +import ( + "errors" + "fmt" + "log" + "strconv" + "strings" + + "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"` + AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"` + ISOStoragePool string `mapstructure:"iso_storage_pool"` + UnmountISO bool `mapstructure:"unmount_iso"` + shouldUploadISO bool +} + +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, []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 +} diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go new file mode 100644 index 000000000..2616baf91 --- /dev/null +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -0,0 +1,274 @@ +// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; 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"` + 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"` + Data interface{} `cty:"data" hcl:"data"` + Funcs map[string]interface{} `cty:"funcs" hcl:"funcs"` + UserVariables map[string]string `cty:"user_variables" hcl:"user_variables"` + SensitiveVariables []string `cty:"sensitive_variables" hcl:"sensitive_variables"` + EnableEnv *bool `cty:"enable_env" hcl:"enable_env"` + BuildName *string `cty:"build_name" hcl:"build_name"` + BuildType *string `cty:"build_type" hcl:"build_type"` + TemplatePath *string `cty:"template_path" hcl:"template_path"` + 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"` + AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` + 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}, + "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}, + "data": &hcldec.AttrSpec{Name: "data", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "funcs": &hcldec.AttrSpec{Name: "funcs", Type: cty.Map(cty.String), Required: false}, + "user_variables": &hcldec.AttrSpec{Name: "user_variables", Type: cty.Map(cty.String), Required: false}, + "sensitive_variables": &hcldec.AttrSpec{Name: "sensitive_variables", Type: cty.List(cty.String), Required: false}, + "enable_env": &hcldec.AttrSpec{Name: "enable_env", Type: cty.Bool, Required: false}, + "build_name": &hcldec.AttrSpec{Name: "build_name", Type: cty.String, Required: false}, + "build_type": &hcldec.AttrSpec{Name: "build_type", Type: cty.String, Required: false}, + "template_path": &hcldec.AttrSpec{Name: "template_path", 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}, + "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())}, + "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 +} + +// FlatstorageConfig is an auto-generated flat version of storageConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatstorageConfig struct { + 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"` + Device *string `mapstructure:"device" cty:"device" hcl:"device"` + 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"` +} + +// FlatMapstructure returns a new FlatstorageConfig. +// FlatstorageConfig is an auto-generated flat version of storageConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatstorageConfig) +} + +// HCL2Spec returns the hcl spec of a storageConfig. +// This spec is used by HCL to read the fields of storageConfig. +// The decoded values from this spec will then be applied to a FlatstorageConfig. +func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "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}, + "device": &hcldec.AttrSpec{Name: "device", 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": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, + } + return s +} diff --git a/builder/proxmox/config_test.go b/builder/proxmox/iso/config_test.go similarity index 53% rename from builder/proxmox/config_test.go rename to builder/proxmox/iso/config_test.go index e17d2e82f..836436422 100644 --- a/builder/proxmox/config_test.go +++ b/builder/proxmox/iso/config_test.go @@ -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: @@ -148,63 +110,3 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { t.Errorf("Expected CloudInit to be false, got %t", b.config.CloudInit) } } - -func TestAgentSetToFalse(t *testing.T) { - cfg := mandatoryConfig(t) - cfg["qemu_agent"] = false - - var c Config - warn, err := c.Prepare(cfg) - if err != nil { - t.Fatal(err, warn) - } - - 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(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()) - } - } -} diff --git a/builder/proxmox/iso/step_finalize_iso.go b/builder/proxmox/iso/step_finalize_iso.go new file mode 100644 index 000000000..88ef44bd2 --- /dev/null +++ b/builder/proxmox/iso/step_finalize_iso.go @@ -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) { +} diff --git a/builder/proxmox/iso/step_finalize_iso_test.go b/builder/proxmox/iso/step_finalize_iso_test.go new file mode 100644 index 000000000..a35a7f5a2 --- /dev/null +++ b/builder/proxmox/iso/step_finalize_iso_test.go @@ -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) + } + }) + } +} diff --git a/builder/proxmox/step_upload_additional_isos.go b/builder/proxmox/iso/step_upload_additional_isos.go similarity index 90% rename from builder/proxmox/step_upload_additional_isos.go rename to builder/proxmox/iso/step_upload_additional_isos.go index b867032c0..c6e808ffc 100644 --- a/builder/proxmox/step_upload_additional_isos.go +++ b/builder/proxmox/iso/step_upload_additional_isos.go @@ -1,9 +1,8 @@ -package proxmox +package proxmoxiso import ( "context" "fmt" - "io" "os" "path/filepath" @@ -16,16 +15,12 @@ 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 { diff --git a/builder/proxmox/step_upload_iso.go b/builder/proxmox/iso/step_upload_iso.go similarity index 88% rename from builder/proxmox/step_upload_iso.go rename to builder/proxmox/iso/step_upload_iso.go index de947075c..79096bfe0 100644 --- a/builder/proxmox/step_upload_iso.go +++ b/builder/proxmox/iso/step_upload_iso.go @@ -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 } diff --git a/builder/proxmox/step_upload_iso_test.go b/builder/proxmox/iso/step_upload_iso_test.go similarity index 98% rename from builder/proxmox/step_upload_iso_test.go rename to builder/proxmox/iso/step_upload_iso_test.go index a009d6686..418005a0c 100644 --- a/builder/proxmox/step_upload_iso_test.go +++ b/builder/proxmox/iso/step_upload_iso_test.go @@ -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) diff --git a/builder/proxmox/testdata/test.iso b/builder/proxmox/iso/testdata/test.iso similarity index 100% rename from builder/proxmox/testdata/test.iso rename to builder/proxmox/iso/testdata/test.iso diff --git a/command/plugin.go b/command/plugin.go index 82a4ad016..38adb929c 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -48,7 +48,8 @@ import ( parallelsisobuilder "github.com/hashicorp/packer/builder/parallels/iso" 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" @@ -145,7 +146,9 @@ var Builders = map[string]packer.Builder{ "parallels-iso": new(parallelsisobuilder.Builder), "parallels-pvm": new(parallelspvmbuilder.Builder), "profitbricks": new(profitbricksbuilder.Builder), - "proxmox": new(proxmoxbuilder.Builder), + "proxmox-clone": new(proxmoxclonebuilder.Builder), + "proxmox-iso": new(proxmoxisobuilder.Builder), + "proxmox": new(proxmoxisobuilder.Builder), "qemu": new(qemubuilder.Builder), "scaleway": new(scalewaybuilder.Builder), "tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),