diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index d3c14707..986ffb2b 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -271,8 +271,8 @@ boot time. - `cloud_init_disk_type` (string) - The type of Cloud-Init disk. Can be `scsi`, `sata`, or `ide` Defaults to `ide`. -- `additional_iso_files` ([]additionalISOsConfig) - Additional ISO files attached to the virtual machine. - See [Additional ISO Files](#additional-iso-files). +- `additional_iso_files` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISOs](#isos). - `vm_interface` (string) - Name of the network interface that Packer gets the VMs IP from. Defaults to the first non loopback interface. @@ -444,6 +444,12 @@ Example: multiple disks are used. Requires `virtio-scsi-single` controller and a `scsi` or `virtio` disk. Defaults to `false`. +- `asyncio` (string) - Configure Asynchronous I/O. Can be `native`, `threads`, or `io_uring`. + Defaults to io_uring. + +- `exclude_from_backup` (bool) - Exclude disk from Proxmox backup jobs + Defaults to false. + - `discard` (bool) - Relay TRIM commands to the underlying storage. Defaults to false. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_hard_disk_discard) @@ -495,28 +501,40 @@ Usage example (JSON): -### Additional ISO Files +### ISO Files - + -Additional ISO files attached to the virtual machine. +ISO files attached to the virtual machine. -Example: +JSON Example: ```json -[ - { - "device": "scsi5", - "iso_file": "local:iso/virtio-win-0.1.185.iso", - "unmount": true, - "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + "additional_iso_files": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + additional_iso_files { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" } -] ``` - + @@ -651,13 +669,18 @@ In HCL2: - + -- `device` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ideX`, +- `device` (string) - DEPRECATED. Assign bus type with `type`. Optionally assign a bus index with `index`. + Bus type and bus index that the ISO will be mounted on. Can be `ideX`, `sataX` or `scsiX`. For `ide` the bus index ranges from 0 to 3, for `sata` from 0 to 5 and for `scsi` from 0 to 30. - Defaults to `ide3` since `ide2` is generally the boot drive. + Defaulted to `ide3` in versions up to v1.8, now defaults to dynamic ide assignment (next available ide bus index after hard disks are allocated) + +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + +- `index` (string) - Optional: Used in combination with `type` to statically assign an ISO to a bus index. - `iso_file` (string) - Path to the ISO file to boot from, expressed as a proxmox datastore path, for example @@ -673,7 +696,10 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. - +- `keep_cdrom_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + + diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 2336d807..b9baa7a0 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -35,33 +35,37 @@ in the image's Cloud-Init settings for provisioning. ### Required: - - -- `iso_checksum` (string) - The checksum for the ISO file or virtual hard drive file. The type of - the checksum is specified within the checksum field as a prefix, ex: - "md5:{$checksum}". The type of the checksum can also be omitted and - Packer will try to infer it based on string length. Valid values are - "none", "{$checksum}", "md5:{$checksum}", "sha1:{$checksum}", - "sha256:{$checksum}", "sha512:{$checksum}" or "file:{$path}". Here is a - list of valid checksum values: - * md5:090992ba9fd140077b0661cb75f7ce13 - * 090992ba9fd140077b0661cb75f7ce13 - * sha1:ebfb681885ddf1234c18094a45bbeafd91467911 - * ebfb681885ddf1234c18094a45bbeafd91467911 - * sha256:ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 - * ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 - * file:http://releases.ubuntu.com/20.04/SHA256SUMS - * file:file://./local/path/file.sum - * file:./local/path/file.sum - * none - Although the checksum will not be verified when it is set to "none", - this is not recommended since these files can be very large and - corruption does happen from time to time. + -- `iso_url` (string) - A URL to the ISO containing the installation image or virtual hard drive - (VHD or VHDX) file to clone. +- `boot_iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. + + JSON Example: + + ```json + + "boot_iso": { + "type": "scsi", + "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + "unmount": true, + "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + HCL2 example: + + ```hcl + + boot_iso { + type = "scsi" + iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" + unmount = true + iso_checksum = "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + See [ISOs](#isos) for additional options. - + ### Optional: @@ -202,8 +206,8 @@ in the image's Cloud-Init settings for provisioning. - `cloud_init_disk_type` (string) - The type of Cloud-Init disk. Can be `scsi`, `sata`, or `ide` Defaults to `ide`. -- `additional_iso_files` ([]additionalISOsConfig) - Additional ISO files attached to the virtual machine. - See [Additional ISO Files](#additional-iso-files). +- `additional_iso_files` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISOs](#isos). - `vm_interface` (string) - Name of the network interface that Packer gets the VMs IP from. Defaults to the first non loopback interface. @@ -217,19 +221,23 @@ in the image's Cloud-Init settings for provisioning. -- `iso_file` (string) - Path to the ISO file to boot from, expressed as a +- `iso_file` (string) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Path to the ISO file to boot from, expressed as a proxmox datastore path, for example `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. -- `iso_storage_pool` (string) - Proxmox storage pool onto which to upload +- `iso_storage_pool` (string) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Proxmox storage pool onto which to upload the ISO file. -- `iso_download_pve` (bool) - Download the ISO directly from the PVE node rather than through Packer. +- `iso_download_pve` (bool) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Download the ISO directly from the PVE node rather than through Packer. Defaults to `false` -- `unmount_iso` (bool) - If true, remove the mounted ISO from the template +- `unmount_iso` (bool) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + If true, remove the mounted ISO from the template after finishing. Defaults to `false`. @@ -252,110 +260,41 @@ in the image's Cloud-Init settings for provisioning. -### Additional ISO Files - - - -Additional ISO files attached to the virtual machine. - -Example: - -```json -[ - - { - "device": "scsi5", - "iso_file": "local:iso/virtio-win-0.1.185.iso", - "unmount": true, - "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" - } - -] -``` - - - - - - -By default, Packer will symlink, download or copy image files to the Packer -cache into a "`hash($iso_url+$iso_checksum).$iso_target_extension`" file. -Packer uses [hashicorp/go-getter](https://github.com/hashicorp/go-getter) in -file mode in order to perform a download. - -go-getter supports the following protocols: - -* Local files -* Git -* Mercurial -* HTTP -* Amazon S3 - -Examples: -go-getter can guess the checksum type based on `iso_checksum` length, and it is -also possible to specify the checksum type. - -In JSON: - -```json +### ISOs - "iso_checksum": "946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2", - "iso_url": "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" + -``` +ISO files attached to the virtual machine. -```json - - "iso_checksum": "file:ubuntu.org/..../ubuntu-14.04.1-server-amd64.iso.sum", - "iso_url": "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" - -``` +JSON Example: ```json - "iso_checksum": "file://./shasums.txt", - "iso_url": "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" - -``` - -```json - - "iso_checksum": "file:./shasums.txt", - "iso_url": "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" - -``` - -In HCL2: - -```hcl - - iso_checksum = "946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2" - iso_url = "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" + "additional_iso_files": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] ``` +HCL2 example: ```hcl - iso_checksum = "file:ubuntu.org/..../ubuntu-14.04.1-server-amd64.iso.sum" - iso_url = "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" + additional_iso_files { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } ``` -```hcl - - iso_checksum = "file://./shasums.txt" - iso_url = "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" + -``` - -```hcl - - iso_checksum = "file:./shasums.txt", - iso_url = "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" - -``` - - #### Required @@ -408,13 +347,18 @@ In HCL2: - + -- `device` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ideX`, +- `device` (string) - DEPRECATED. Assign bus type with `type`. Optionally assign a bus index with `index`. + Bus type and bus index that the ISO will be mounted on. Can be `ideX`, `sataX` or `scsiX`. For `ide` the bus index ranges from 0 to 3, for `sata` from 0 to 5 and for `scsi` from 0 to 30. - Defaults to `ide3` since `ide2` is generally the boot drive. + Defaulted to `ide3` in versions up to v1.8, now defaults to dynamic ide assignment (next available ide bus index after hard disks are allocated) + +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + +- `index` (string) - Optional: Used in combination with `type` to statically assign an ISO to a bus index. - `iso_file` (string) - Path to the ISO file to boot from, expressed as a proxmox datastore path, for example @@ -430,7 +374,10 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. - +- `keep_cdrom_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + + @@ -659,6 +606,12 @@ Example: multiple disks are used. Requires `virtio-scsi-single` controller and a `scsi` or `virtio` disk. Defaults to `false`. +- `asyncio` (string) - Configure Asynchronous I/O. Can be `native`, `threads`, or `io_uring`. + Defaults to io_uring. + +- `exclude_from_backup` (bool) - Exclude disk from Proxmox backup jobs + Defaults to false. + - `discard` (bool) - Relay TRIM commands to the underlying storage. Defaults to false. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_hard_disk_discard) @@ -1125,7 +1078,9 @@ source "proxmox-iso" "fedora-kickstart" { } http_directory = "config" insecure_skip_tls_verify = true - iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + iso { + iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } network_adapters { bridge = "vmbr0" model = "virtio" @@ -1138,7 +1093,6 @@ source "proxmox-iso" "fedora-kickstart" { ssh_username = "root" template_description = "Fedora 29-1.2, generated on ${timestamp()}" template_name = "fedora-29" - unmount_iso = true username = "${var.username}" } @@ -1181,7 +1135,9 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "iso": { + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + }, "http_directory": "config", "boot_wait": "10s", "boot_command": [ @@ -1190,7 +1146,6 @@ build { "ssh_username": "root", "ssh_timeout": "15m", "ssh_password": "packer", - "unmount_iso": true, "template_name": "fedora-29", "template_description": "Fedora 29-1.2, generated on {{ isotime \"2006-01-02T15:04:05Z\" }}" } diff --git a/builder/proxmox/clone/builder.go b/builder/proxmox/clone/builder.go index ac46376c..bfc94d6f 100644 --- a/builder/proxmox/clone/builder.go +++ b/builder/proxmox/clone/builder.go @@ -39,6 +39,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) Debug: b.config.PackerDebug, DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), }, + &StepMapSourceDisks{}, } postSteps := []multistep.Step{} diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index e1e28d45..e131cb6b 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -11,121 +11,121 @@ import ( // 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"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - 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"` - HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` - HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` - HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` - HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` - HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` - BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` - BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` - BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` - BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` - Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` - SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` - SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` - 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"` - Token *string `mapstructure:"token" cty:"token" hcl:"token"` - Node *string `mapstructure:"node" cty:"node" hcl:"node"` - Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` - TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` - VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` - VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` - Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` - Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` - Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` - BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` - 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"` - Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` - OS *string `mapstructure:"os" cty:"os" hcl:"os"` - BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` - EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` - EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` - Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` - Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` - TPMConfig *proxmox.FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` - 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"` - PCIDevices []proxmox.FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` - Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` - 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"` - CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` - AdditionalISOFiles []proxmox.FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` - VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` - AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` - CloneVM *string `mapstructure:"clone_vm" required:"true" cty:"clone_vm" hcl:"clone_vm"` - CloneVMID *int `mapstructure:"clone_vm_id" required:"true" cty:"clone_vm_id" hcl:"clone_vm_id"` - FullClone *bool `mapstructure:"full_clone" required:"false" cty:"full_clone" hcl:"full_clone"` - Nameserver *string `mapstructure:"nameserver" required:"false" cty:"nameserver" hcl:"nameserver"` - Searchdomain *string `mapstructure:"searchdomain" required:"false" cty:"searchdomain" hcl:"searchdomain"` - Ipconfigs []FlatcloudInitIpconfig `mapstructure:"ipconfig" required:"false" cty:"ipconfig" hcl:"ipconfig"` + 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"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + 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"` + HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` + HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` + HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` + BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` + Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` + SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` + SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` + 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"` + Token *string `mapstructure:"token" cty:"token" hcl:"token"` + Node *string `mapstructure:"node" cty:"node" hcl:"node"` + Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` + TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` + Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` + Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` + Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` + BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` + 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"` + Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` + OS *string `mapstructure:"os" cty:"os" hcl:"os"` + BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` + EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` + EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` + Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` + TPMConfig *proxmox.FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` + 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"` + PCIDevices []proxmox.FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` + Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` + 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"` + CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` + ISOs []proxmox.FlatISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` + VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` + AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + CloneVM *string `mapstructure:"clone_vm" required:"true" cty:"clone_vm" hcl:"clone_vm"` + CloneVMID *int `mapstructure:"clone_vm_id" required:"true" cty:"clone_vm_id" hcl:"clone_vm_id"` + FullClone *bool `mapstructure:"full_clone" required:"false" cty:"full_clone" hcl:"full_clone"` + Nameserver *string `mapstructure:"nameserver" required:"false" cty:"nameserver" hcl:"nameserver"` + Searchdomain *string `mapstructure:"searchdomain" required:"false" cty:"searchdomain" hcl:"searchdomain"` + Ipconfigs []FlatcloudInitIpconfig `mapstructure:"ipconfig" required:"false" cty:"ipconfig" hcl:"ipconfig"` } // FlatMapstructure returns a new FlatConfig. @@ -246,7 +246,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, "cloud_init_disk_type": &hcldec.AttrSpec{Name: "cloud_init_disk_type", Type: cty.String, Required: false}, - "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatadditionalISOsConfig)(nil).HCL2Spec())}, + "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", Type: cty.String, Required: false}, "clone_vm": &hcldec.AttrSpec{Name: "clone_vm", Type: cty.String, Required: false}, diff --git a/builder/proxmox/clone/step_map_source_disks.go b/builder/proxmox/clone/step_map_source_disks.go new file mode 100644 index 00000000..6e73fd4a --- /dev/null +++ b/builder/proxmox/clone/step_map_source_disks.go @@ -0,0 +1,98 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proxmoxclone + +import ( + "context" + "fmt" + "log" + "regexp" + "strings" + + proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" + proxmox "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +// StepMapSourceDisks retrieves the configuration of the clone source vm +// and identifies any attached disks to prevent hcl/json defined disks +// and isos from overwriting their assignments. +// (Enables append behavior for hcl/json defined disks and ISOs) +type StepMapSourceDisks struct{} + +type cloneSource interface { + GetVmConfig(*proxmoxapi.VmRef) (map[string]interface{}, error) + GetVmRefsByName(string) ([]*proxmoxapi.VmRef, error) + CheckVmRef(*proxmoxapi.VmRef) error +} + +var _ cloneSource = &proxmoxapi.Client{} + +func (s *StepMapSourceDisks) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packersdk.Ui) + client := state.Get("proxmoxClient").(cloneSource) + c := state.Get("clone-config").(*Config) + + var sourceVmr *proxmoxapi.VmRef + if c.CloneVM != "" { + sourceVmrs, err := client.GetVmRefsByName(c.CloneVM) + if err != nil { + state.Put("error", fmt.Errorf("Could not retrieve VM: %s", err)) + return multistep.ActionHalt + } + // prefer source Vm located on same node + sourceVmr = sourceVmrs[0] + for _, candVmr := range sourceVmrs { + if candVmr.Node() == c.Node { + sourceVmr = candVmr + } + } + } else if c.CloneVMID != 0 { + sourceVmr = proxmoxapi.NewVmRef(c.CloneVMID) + err := client.CheckVmRef(sourceVmr) + if err != nil { + state.Put("error", fmt.Errorf("Could not retrieve VM: %s", err)) + return multistep.ActionHalt + } + } + + vmParams, err := client.GetVmConfig(sourceVmr) + if err != nil { + err := fmt.Errorf("error fetching template config: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + var sourceDisks []string + + // example v data returned for a disk: + // local-lvm:base-9100-disk-1,backup=0,cache=none,discard=on,replicate=0,size=16G + // example v data returned for a cloud-init disk: + // local-lvm:vm-9100-cloudinit,media=cdrom + // example v data returned for a cdrom: + // local-lvm:iso/ubuntu-14.04.1-server-amd64.iso,media=cdrom,size=572M + + // preserve only disk assignments, cloud-init drives are recreated by common builder + for k, v := range vmParams { + // get device from k eg. ide from ide2 + rd := regexp.MustCompile(`\D+`) + switch rd.FindString(k) { + case "ide", "sata", "scsi", "virtio": + if !strings.Contains(v.(string), "media=cdrom") { + log.Println("disk discovered on source vm at", k) + sourceDisks = append(sourceDisks, k) + } + } + } + + // store discovered disks in common config + d := state.Get("config").(*proxmox.Config) + d.CloneSourceDisks = sourceDisks + + return multistep.ActionContinue +} + +func (s *StepMapSourceDisks) Cleanup(state multistep.StateBag) {} diff --git a/builder/proxmox/common/builder.go b/builder/proxmox/common/builder.go index 0fed4d77..3bfb55d4 100644 --- a/builder/proxmox/common/builder.go +++ b/builder/proxmox/common/builder.go @@ -75,30 +75,30 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook, &stepSuccess{}, } preSteps := b.preSteps - for idx := range b.config.AdditionalISOFiles { - if b.config.AdditionalISOFiles[idx].ISODownloadPVE { + for idx := range b.config.ISOs { + if b.config.ISOs[idx].ISODownloadPVE { preSteps = append(preSteps, &stepDownloadISOOnPVE{ - ISO: &b.config.AdditionalISOFiles[idx], + ISO: &b.config.ISOs[idx], }, ) } else { preSteps = append(preSteps, &commonsteps.StepCreateCD{ - Files: b.config.AdditionalISOFiles[idx].CDConfig.CDFiles, - Content: b.config.AdditionalISOFiles[idx].CDConfig.CDContent, - Label: b.config.AdditionalISOFiles[idx].CDConfig.CDLabel, + Files: b.config.ISOs[idx].CDConfig.CDFiles, + Content: b.config.ISOs[idx].CDConfig.CDContent, + Label: b.config.ISOs[idx].CDConfig.CDLabel, }, &commonsteps.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, + Checksum: b.config.ISOs[idx].ISOChecksum, + Description: "ISO", + Extension: b.config.ISOs[idx].TargetExtension, + ResultKey: b.config.ISOs[idx].DownloadPathKey, + TargetPath: b.config.ISOs[idx].DownloadPathKey, + Url: b.config.ISOs[idx].ISOUrls, }, - &stepUploadAdditionalISO{ - ISO: &b.config.AdditionalISOFiles[idx], + &stepUploadISO{ + ISO: &b.config.ISOs[idx], }, ) } diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 3890928f..f8104fe8 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 //go:generate packer-sdc struct-markdown -//go:generate packer-sdc mapstructure-to-hcl2 -type Config,NICConfig,diskConfig,rng0Config,pciDeviceConfig,vgaConfig,additionalISOsConfig,efiConfig,tpmConfig +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,NICConfig,diskConfig,rng0Config,pciDeviceConfig,vgaConfig,ISOsConfig,efiConfig,tpmConfig package proxmox @@ -189,9 +189,9 @@ type Config struct { // Defaults to `ide`. CloudInitDiskType string `mapstructure:"cloud_init_disk_type"` - // Additional ISO files attached to the virtual machine. - // See [Additional ISO Files](#additional-iso-files). - AdditionalISOFiles []additionalISOsConfig `mapstructure:"additional_iso_files"` + // ISO files attached to the virtual machine. + // See [ISOs](#isos). + ISOs []ISOsConfig `mapstructure:"additional_iso_files"` // Name of the network interface that Packer gets // the VMs IP from. Defaults to the first non loopback interface. VMInterface string `mapstructure:"vm_interface"` @@ -201,33 +201,53 @@ type Config struct { // Note: this option is for experts only. AdditionalArgs string `mapstructure:"qemu_additional_args"` + // Used by clone builder StepMapSourceDisks to store existing disk assignments + CloneSourceDisks []string `mapstructure-to-hcl2:",skip"` + Ctx interpolate.Context `mapstructure-to-hcl2:",skip"` } -// Additional ISO files attached to the virtual machine. +// ISO files attached to the virtual machine. // -// Example: +// JSON Example: // // ```json -// [ // -// { -// "device": "scsi5", -// "iso_file": "local:iso/virtio-win-0.1.185.iso", -// "unmount": true, -// "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" +// "additional_iso_files": [ +// { +// "type": "scsi", +// "iso_file": "local:iso/virtio-win-0.1.185.iso", +// "unmount": true, +// "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" +// } +// ] +// +// ``` +// HCL2 example: +// +// ```hcl +// +// additional_iso_files { +// type = "scsi" +// iso_file = "local:iso/virtio-win-0.1.185.iso" +// unmount = true +// iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" // } // -// ] // ``` -type additionalISOsConfig struct { +type ISOsConfig struct { commonsteps.ISOConfig `mapstructure:",squash"` + // DEPRECATED. Assign bus type with `type`. Optionally assign a bus index with `index`. // Bus type and bus index that the ISO will be mounted on. Can be `ideX`, // `sataX` or `scsiX`. // For `ide` the bus index ranges from 0 to 3, for `sata` from 0 to 5 and for // `scsi` from 0 to 30. - // Defaults to `ide3` since `ide2` is generally the boot drive. + // Defaulted to `ide3` in versions up to v1.8, now defaults to dynamic ide assignment (next available ide bus index after hard disks are allocated) Device string `mapstructure:"device"` + // Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + Type string `mapstructure:"type"` + // Optional: Used in combination with `type` to statically assign an ISO to a bus index. + Index string `mapstructure:"index"` // Path to the ISO file to boot from, expressed as a // proxmox datastore path, for example // `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. @@ -241,9 +261,13 @@ type additionalISOsConfig struct { // Defaults to `false` ISODownloadPVE bool `mapstructure:"iso_download_pve"` // If true, remove the mounted ISO from the template after finishing. Defaults to `false`. - Unmount bool `mapstructure:"unmount"` + Unmount bool `mapstructure:"unmount"` + // Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + // Has no effect if unmount is `false` + KeepCDRomDevice bool `mapstructure:"keep_cdrom_device"` ShouldUploadISO bool `mapstructure-to-hcl2:",skip"` DownloadPathKey string `mapstructure-to-hcl2:",skip"` + AssignedDeviceIndex string `mapstructure-to-hcl2:",skip"` commonsteps.CDConfig `mapstructure:",squash"` } @@ -339,6 +363,12 @@ type diskConfig struct { // multiple disks are used. Requires `virtio-scsi-single` controller and a // `scsi` or `virtio` disk. Defaults to `false`. IOThread bool `mapstructure:"io_thread"` + // Configure Asynchronous I/O. Can be `native`, `threads`, or `io_uring`. + // Defaults to io_uring. + AsyncIO string `mapstructure:"asyncio"` + // Exclude disk from Proxmox backup jobs + // Defaults to false. + ExcludeFromBackup bool `mapstructure:"exclude_from_backup"` // Relay TRIM commands to the underlying storage. Defaults // to false. See the // [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_hard_disk_discard) @@ -636,12 +666,113 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st log.Printf("OS not set, using default 'other'") c.OS = "other" } - ideCount := 0 - sataCount := 0 - scsiCount := 0 - virtIOCount := 0 + // validate iso devices + for idx := range c.ISOs { + // 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.ISOs[idx].ISOFile != "" { + c.ISOs[idx].ShouldUploadISO = false + } else { + c.ISOs[idx].DownloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx) + if len(c.ISOs[idx].CDFiles) > 0 || len(c.ISOs[idx].CDContent) > 0 { + cdErrors := c.ISOs[idx].CDConfig.Prepare(&c.Ctx) + errs = packersdk.MultiErrorAppend(errs, cdErrors...) + } else { + isoWarnings, isoErrors := c.ISOs[idx].ISOConfig.Prepare(&c.Ctx) + errs = packersdk.MultiErrorAppend(errs, isoErrors...) + warnings = append(warnings, isoWarnings...) + } + c.ISOs[idx].ShouldUploadISO = true + } + // validate device field + if c.ISOs[idx].Device != "" { + warnings = append(warnings, "additional_iso_files field 'device' is deprecated and will be removed in a future release, assign bus type with 'type'. Optionally assign a bus index with 'index'") + if strings.HasPrefix(c.ISOs[idx].Device, "ide") { + busnumber, err := strconv.Atoi(c.ISOs[idx].Device[3:]) + if err != nil { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.ISOs[idx].Device[3:])) + } + if busnumber > 3 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3")) + } else { + // convert device field to type and index fields + log.Printf("converting deprecated field 'device' value %s to 'type' %s and 'index' %d", c.ISOs[idx].Device, "ide", busnumber) + c.ISOs[idx].Type = "ide" + c.ISOs[idx].Index = strconv.Itoa(busnumber) + } + } + if strings.HasPrefix(c.ISOs[idx].Device, "sata") { + busnumber, err := strconv.Atoi(c.ISOs[idx].Device[4:]) + if err != nil { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.ISOs[idx].Device[4:])) + } + if busnumber > 5 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5")) + } else { + // convert device field to type and index fields + log.Printf("converting deprecated field 'device' value %s to 'type' %s and 'index' %d", c.ISOs[idx].Device, "sata", busnumber) + c.ISOs[idx].Type = "sata" + c.ISOs[idx].Index = strconv.Itoa(busnumber) + } + } + if strings.HasPrefix(c.ISOs[idx].Device, "scsi") { + busnumber, err := strconv.Atoi(c.ISOs[idx].Device[4:]) + if err != nil { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.ISOs[idx].Device[4:])) + } + if busnumber > 30 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30")) + } else { + // convert device field to type and index fields + log.Printf("converting deprecated field 'device' value %s to 'type' %s and 'index' %d", c.ISOs[idx].Device, "scsi", busnumber) + c.ISOs[idx].Type = "scsi" + c.ISOs[idx].Index = strconv.Itoa(busnumber) + } + } + } + // validate device type, assign if unset + switch c.ISOs[idx].Type { + case "ide", "sata", "scsi": + case "": + log.Printf("additional_iso %d device type not set, using default 'ide'", idx) + c.ISOs[idx].Type = "ide" + default: + errs = packersdk.MultiErrorAppend(errs, errors.New("ISOs must be of type ide, sata or scsi. VirtIO not supported by Proxmox for ISO devices")) + } + if len(c.ISOs[idx].CDFiles) > 0 || len(c.ISOs[idx].CDContent) > 0 { + if c.ISOs[idx].ISOStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage_pool not set for storage of generated ISO from cd_files or cd_content")) + } + } + if len(c.ISOs[idx].ISOUrls) != 0 && c.ISOs[idx].ISOStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url in an additional_isos block, iso_storage_pool must also be specified")) + } + // Check only one option is present + options := 0 + if c.ISOs[idx].ISOFile != "" { + options++ + } + if len(c.ISOs[idx].ISOConfig.ISOUrls) > 0 || c.ISOs[idx].ISOConfig.RawSingleISOUrl != "" { + options++ + } + if len(c.ISOs[idx].CDFiles) > 0 || len(c.ISOs[idx].CDContent) > 0 { + options++ + } + if options != 1 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("one of iso_file, iso_url, or a combination of cd_files and cd_content must be specified for additional_iso %d", idx)) + } + if len(c.ISOs[idx].ISOConfig.ISOUrls) == 0 && c.ISOs[idx].ISOConfig.RawSingleISOUrl == "" && c.ISOs[idx].ISODownloadPVE { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("iso_download_pve can only be used together with iso_url")) + } + } + + // validate disks for idx, disk := range c.Disks { - if disk.Type == "" { + switch disk.Type { + case "ide", "sata", "scsi", "virtio": + default: log.Printf("Disk %d type not set, using default 'scsi'", idx) c.Disks[idx].Type = "scsi" } @@ -664,6 +795,14 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } } } + if disk.AsyncIO == "" { + disk.AsyncIO = "io_uring" + } + switch disk.AsyncIO { + case "native", "threads", "io_uring": + default: + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("AsyncIO must be native, threads or io_uring")) + } if disk.SSD && disk.Type == "virtio" { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("SSD emulation is not supported on virtio disks")) } @@ -673,29 +812,8 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st if disk.StoragePoolType != "" { warnings = append(warnings, "storage_pool_type is deprecated and should be omitted, it will be removed in a later version of the proxmox plugin") } - switch disk.Type { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - case "virtio": - virtIOCount++ - } - } - if ideCount > 2 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 2 IDE disks supported (ide2,3 reserved for ISOs)")) - } - if sataCount > 6 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 6 SATA disks supported")) - } - if scsiCount > 31 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 31 SCSI disks supported")) - } - if virtIOCount > 16 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 16 VirtIO disks supported")) } + if len(c.Serials) > 4 { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("too many serials: %d serials defined, but proxmox accepts 4 elements maximum", len(c.Serials))) } @@ -764,85 +882,6 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st errs = packersdk.MultiErrorAppend(errs, errors.New("network_adapters[%d].mtu only positive values up to 65520 are supported")) } } - 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) - if len(c.AdditionalISOFiles[idx].CDFiles) > 0 || len(c.AdditionalISOFiles[idx].CDContent) > 0 { - cdErrors := c.AdditionalISOFiles[idx].CDConfig.Prepare(&c.Ctx) - errs = packersdk.MultiErrorAppend(errs, cdErrors...) - } else { - isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.Ctx) - errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:])) - } - if busnumber == 2 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO")) - } - if busnumber > 3 { - errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:])) - } - if busnumber > 5 { - errs = packersdk.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 = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:])) - } - if busnumber > 30 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30")) - } - } - if len(c.AdditionalISOFiles[idx].CDFiles) > 0 || len(c.AdditionalISOFiles[idx].CDContent) > 0 { - if c.AdditionalISOFiles[idx].ISOStoragePool == "" { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("iso_storage_pool not set for storage of generated ISO from cd_files or cd_content")) - } - } - if len(c.AdditionalISOFiles[idx].ISOUrls) != 0 && c.AdditionalISOFiles[idx].ISOStoragePool == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url in an additional_iso_files block, iso_storage_pool must also be specified")) - } - // Check only one option is present - options := 0 - if c.AdditionalISOFiles[idx].ISOFile != "" { - options++ - } - if len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) > 0 || c.AdditionalISOFiles[idx].ISOConfig.RawSingleISOUrl != "" { - options++ - } - if len(c.AdditionalISOFiles[idx].CDFiles) > 0 || len(c.AdditionalISOFiles[idx].CDContent) > 0 { - options++ - } - if options != 1 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("one of iso_file, iso_url, or a combination of cd_files and cd_content must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device)) - } - if len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0 && c.AdditionalISOFiles[idx].ISOConfig.RawSingleISOUrl == "" && c.AdditionalISOFiles[idx].ISODownloadPVE { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("iso_download_pve can only be used together with iso_url")) - } - } if c.EFIDisk != "" { if c.EFIConfig != (efiConfig{}) { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("both efi_config and efidisk cannot be set at the same time, consider defining only efi_config as efidisk is deprecated")) diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index e933239f..11ce4605 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -10,115 +10,115 @@ import ( // 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"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - 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"` - HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` - HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` - HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` - HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` - HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` - BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` - BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` - BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` - BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` - Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` - SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` - SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` - 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"` - Token *string `mapstructure:"token" cty:"token" hcl:"token"` - Node *string `mapstructure:"node" cty:"node" hcl:"node"` - Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` - TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` - VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` - VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` - Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` - Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` - Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` - BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` - 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"` - Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` - OS *string `mapstructure:"os" cty:"os" hcl:"os"` - BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` - EFIConfig *FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` - EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` - Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` - Rng0 *Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` - TPMConfig *FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` - 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"` - PCIDevices []FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` - Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` - 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"` - CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` - AdditionalISOFiles []FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` - VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` - AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + 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"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + 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"` + HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` + HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` + HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` + BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` + Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` + SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` + SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` + 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"` + Token *string `mapstructure:"token" cty:"token" hcl:"token"` + Node *string `mapstructure:"node" cty:"node" hcl:"node"` + Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` + TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` + Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` + Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` + Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` + BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` + 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"` + Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` + OS *string `mapstructure:"os" cty:"os" hcl:"os"` + BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` + EFIConfig *FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` + EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` + Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` + TPMConfig *FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` + 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"` + PCIDevices []FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` + Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` + 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"` + CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` + ISOs []FlatISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` + VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` + AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` } // FlatMapstructure returns a new FlatConfig. @@ -239,77 +239,45 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, "cloud_init_disk_type": &hcldec.AttrSpec{Name: "cloud_init_disk_type", Type: cty.String, Required: false}, - "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatadditionalISOsConfig)(nil).HCL2Spec())}, + "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", 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"` - MTU *int `mapstructure:"mtu" cty:"mtu" hcl:"mtu"` - 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}, - "mtu": &hcldec.AttrSpec{Name: "mtu", Type: cty.Number, 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 -} - -// FlatadditionalISOsConfig is an auto-generated flat version of additionalISOsConfig. +// FlatISOsConfig is an auto-generated flat version of ISOsConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. -type FlatadditionalISOsConfig struct { +type FlatISOsConfig 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"` + Type *string `mapstructure:"type" cty:"type" hcl:"type"` + Index *string `mapstructure:"index" cty:"index" hcl:"index"` 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"` ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"` + KeepCDRomDevice *bool `mapstructure:"keep_cdrom_device" cty:"keep_cdrom_device" hcl:"keep_cdrom_device"` CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"` CDContent map[string]string `mapstructure:"cd_content" cty:"cd_content" hcl:"cd_content"` CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"` } -// FlatMapstructure returns a new FlatadditionalISOsConfig. -// FlatadditionalISOsConfig is an auto-generated flat version of additionalISOsConfig. +// FlatMapstructure returns a new FlatISOsConfig. +// FlatISOsConfig is an auto-generated flat version of ISOsConfig. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*additionalISOsConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { - return new(FlatadditionalISOsConfig) +func (*ISOsConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatISOsConfig) } -// HCL2Spec returns the hcl spec of a additionalISOsConfig. -// This spec is used by HCL to read the fields of additionalISOsConfig. -// The decoded values from this spec will then be applied to a FlatadditionalISOsConfig. -func (*FlatadditionalISOsConfig) HCL2Spec() map[string]hcldec.Spec { +// HCL2Spec returns the hcl spec of a ISOsConfig. +// This spec is used by HCL to read the fields of ISOsConfig. +// The decoded values from this spec will then be applied to a FlatISOsConfig. +func (*FlatISOsConfig) 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}, @@ -317,10 +285,13 @@ func (*FlatadditionalISOsConfig) HCL2Spec() map[string]hcldec.Spec { "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}, + "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, + "index": &hcldec.AttrSpec{Name: "index", 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}, "iso_download_pve": &hcldec.AttrSpec{Name: "iso_download_pve", Type: cty.Bool, Required: false}, "unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, + "keep_cdrom_device": &hcldec.AttrSpec{Name: "keep_cdrom_device", Type: cty.Bool, Required: false}, "cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false}, "cd_content": &hcldec.AttrSpec{Name: "cd_content", Type: cty.Map(cty.String), Required: false}, "cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false}, @@ -328,18 +299,55 @@ func (*FlatadditionalISOsConfig) HCL2Spec() map[string]hcldec.Spec { 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"` + MTU *int `mapstructure:"mtu" cty:"mtu" hcl:"mtu"` + 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}, + "mtu": &hcldec.AttrSpec{Name: "mtu", Type: cty.Number, 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 +} + // 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"` - IOThread *bool `mapstructure:"io_thread" cty:"io_thread" hcl:"io_thread"` - Discard *bool `mapstructure:"discard" cty:"discard" hcl:"discard"` - SSD *bool `mapstructure:"ssd" cty:"ssd" hcl:"ssd"` + 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"` + IOThread *bool `mapstructure:"io_thread" cty:"io_thread" hcl:"io_thread"` + AsyncIO *string `mapstructure:"asyncio" cty:"asyncio" hcl:"asyncio"` + ExcludeFromBackup *bool `mapstructure:"exclude_from_backup" cty:"exclude_from_backup" hcl:"exclude_from_backup"` + Discard *bool `mapstructure:"discard" cty:"discard" hcl:"discard"` + SSD *bool `mapstructure:"ssd" cty:"ssd" hcl:"ssd"` } // FlatMapstructure returns a new FlatdiskConfig. @@ -354,15 +362,17 @@ func (*diskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Sp // 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}, - "io_thread": &hcldec.AttrSpec{Name: "io_thread", Type: cty.Bool, Required: false}, - "discard": &hcldec.AttrSpec{Name: "discard", Type: cty.Bool, Required: false}, - "ssd": &hcldec.AttrSpec{Name: "ssd", Type: cty.Bool, Required: false}, + "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}, + "io_thread": &hcldec.AttrSpec{Name: "io_thread", Type: cty.Bool, Required: false}, + "asyncio": &hcldec.AttrSpec{Name: "asyncio", Type: cty.String, Required: false}, + "exclude_from_backup": &hcldec.AttrSpec{Name: "exclude_from_backup", Type: cty.Bool, Required: false}, + "discard": &hcldec.AttrSpec{Name: "discard", Type: cty.Bool, Required: false}, + "ssd": &hcldec.AttrSpec{Name: "ssd", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 12b6ec94..50cbc39d 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -5,6 +5,7 @@ package proxmox import ( "fmt" + "regexp" "strings" "testing" @@ -137,24 +138,24 @@ func TestVMandTemplateName(t *testing.T) { } } -func TestAdditionalISOs(t *testing.T) { - additionalisotests := []struct { - name string - expectedToFail bool - additionalISOFiles map[string]interface{} +func TestISOs(t *testing.T) { + isotests := []struct { + name string + expectedToFail bool + ISOs map[string]interface{} }{ { name: "missing ISO definition should error", expectedToFail: true, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", }, }, { name: "cd_files and iso_file specified should fail", expectedToFail: true, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "cd_files": []string{ "config_test.go", }, @@ -164,8 +165,8 @@ func TestAdditionalISOs(t *testing.T) { { name: "cd_files, iso_file and iso_url specified should fail", expectedToFail: true, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "cd_files": []string{ "config_test.go", }, @@ -178,8 +179,8 @@ func TestAdditionalISOs(t *testing.T) { { name: "missing iso_storage_pool should error", expectedToFail: true, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "cd_files": []string{ "config_test.go", }, @@ -188,7 +189,74 @@ func TestAdditionalISOs(t *testing.T) { { name: "cd_files valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ + ISOs: map[string]interface{}{ + "type": "ide", + "cd_files": []string{ + "config_test.go", + }, + "iso_storage_pool": "local", + }, + }, + { + name: "cd_content valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ + "type": "ide", + "cd_content": map[string]string{ + "test": "config_test.go", + }, + "iso_storage_pool": "local", + }, + }, + { + name: "iso_url valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ + "type": "ide", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "iso_storage_pool": "local", + }, + }, + { + name: "iso_file valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ + "type": "ide", + "iso_file": "local:iso/test.iso", + }, + }, + } + + for _, c := range isotests { + t.Run(c.name, func(t *testing.T) { + cfg := mandatoryConfig(t) + cfg["additional_iso_files"] = c.ISOs + + var config Config + _, _, err := config.Prepare(&config, cfg) + + if c.expectedToFail && err == nil { + t.Error("expected config preparation to fail, but no error occured") + } + + if !c.expectedToFail && err != nil { + t.Errorf("expected config preparation to succeed, but %s", err.Error()) + } + }) + } +} + +func TestDeprecatedISOOptionsAreConverted(t *testing.T) { + isotests := []struct { + name string + expectedToFail bool + ISOs map[string]interface{} + }{ + { + name: "cd_files valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ "device": "ide1", "cd_files": []string{ "config_test.go", @@ -199,7 +267,7 @@ func TestAdditionalISOs(t *testing.T) { { name: "cd_content valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ + ISOs: map[string]interface{}{ "device": "ide1", "cd_content": map[string]string{ "test": "config_test.go", @@ -210,7 +278,7 @@ func TestAdditionalISOs(t *testing.T) { { name: "iso_url valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ + ISOs: map[string]interface{}{ "device": "ide1", "iso_url": "http://example.com", "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -220,31 +288,46 @@ func TestAdditionalISOs(t *testing.T) { { name: "iso_file valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ + ISOs: map[string]interface{}{ "device": "ide1", "iso_file": "local:iso/test.iso", }, }, } - - for _, c := range additionalisotests { + for _, c := range isotests { t.Run(c.name, func(t *testing.T) { cfg := mandatoryConfig(t) - cfg["additional_iso_files"] = c.additionalISOFiles + cfg["additional_iso_files"] = c.ISOs var config Config _, _, err := config.Prepare(&config, cfg) + if err != nil { + t.Fatal(err) + } + + rd := regexp.MustCompile(`\D+`) + bus := rd.FindString(config.ISOs[0].Device) + + rb := regexp.MustCompile(`\d+`) + index := rb.FindString(config.ISOs[0].Device) + + if config.ISOs[0].Type != bus { + t.Errorf("Expected device to be converted to type %s", bus) + } + if config.ISOs[0].Index != index { + t.Errorf("Expected device to be converted to index %s", index) + } - if c.expectedToFail == true && err == nil { + if c.expectedToFail && err == nil { t.Error("expected config preparation to fail, but no error occured") } - if c.expectedToFail == false && err != nil { + if !c.expectedToFail && err != nil { t.Errorf("expected config preparation to succeed, but %s", err.Error()) } + }) } - } func TestRng0(t *testing.T) { diff --git a/builder/proxmox/common/step_download_iso_on_pve.go b/builder/proxmox/common/step_download_iso_on_pve.go index 18adbd5a..de348f96 100644 --- a/builder/proxmox/common/step_download_iso_on_pve.go +++ b/builder/proxmox/common/step_download_iso_on_pve.go @@ -20,7 +20,7 @@ import ( // stepDownloadISOOnPVE downloads an ISO file directly to the specified PVE node. // Checksums are also calculated and compared on the PVE node, not by Packer. type stepDownloadISOOnPVE struct { - ISO *additionalISOsConfig + ISO *ISOsConfig } func (s *stepDownloadISOOnPVE) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { diff --git a/builder/proxmox/common/step_finalize_template_config.go b/builder/proxmox/common/step_finalize_template_config.go index 9fdcdf8f..ff739043 100644 --- a/builder/proxmox/common/step_finalize_template_config.go +++ b/builder/proxmox/common/step_finalize_template_config.go @@ -105,19 +105,24 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St } } - if len(c.AdditionalISOFiles) > 0 { - for idx := range c.AdditionalISOFiles { - cdrom := c.AdditionalISOFiles[idx].Device - if c.AdditionalISOFiles[idx].Unmount { + deleteItems := []string{} + if len(c.ISOs) > 0 { + for idx := range c.ISOs { + cdrom := c.ISOs[idx].AssignedDeviceIndex + if c.ISOs[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" + if c.ISOs[idx].KeepCDRomDevice { + changes[cdrom] = "none,media=cdrom" + } else { + deleteItems = append(deleteItems, cdrom) + } } else { - changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom" + changes[cdrom] = c.ISOs[idx].ISOFile + ",media=cdrom" } } } @@ -125,13 +130,13 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St // Disks that get replaced by the builder end up as unused disks - // find and remove them. rxUnused := regexp.MustCompile(`^unused\d+`) - unusedDisks := []string{} for key := range vmParams { if unusedDisk := rxUnused.FindString(key); unusedDisk != "" { - unusedDisks = append(unusedDisks, unusedDisk) + deleteItems = append(deleteItems, unusedDisk) } } - changes["delete"] = strings.Join(unusedDisks, ",") + + changes["delete"] = strings.Join(deleteItems, ",") if len(changes) > 0 { _, err := client.SetVmConfig(vmRef, changes) diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 7c94eb0d..85f66d3d 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "reflect" + "slices" "strconv" "strings" @@ -110,6 +111,18 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist kvm = false } + errs, warnings, disks := generateProxmoxDisks(c.Disks, c.ISOs, c.CloneSourceDisks) + if errs != nil && len(errs.Errors) > 0 { + state.Put("error", errs) + ui.Error(errs.Error()) + return multistep.ActionHalt + } + if len(warnings) > 0 { + for idx := range warnings { + ui.Sayf("Warning: %s", warnings[idx]) + } + } + config := proxmox.ConfigQemu{ Name: c.VMName, Agent: agent, @@ -130,7 +143,7 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist TPM: generateProxmoxTpm(c.TPMConfig), QemuVga: generateProxmoxVga(c.VGA), QemuNetworks: generateProxmoxNetworkAdapters(c.NICs), - Disks: generateProxmoxDisks(c.Disks), + Disks: disks, QemuPCIDevices: generateProxmoxPCIDeviceMap(c.PCIDevices), QemuSerials: generateProxmoxSerials(c.Serials), Scsihw: c.SCSIController, @@ -207,22 +220,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist return multistep.ActionHalt } - // proxmox-api-go assumes all QemuDisks are actually hard disks, not cd - // drives, so we need to add them via a config update - if len(c.AdditionalISOFiles) > 0 { - addISOConfig := make(map[string]interface{}) - for _, iso := range c.AdditionalISOFiles { - addISOConfig[iso.Device] = fmt.Sprintf("%s,media=cdrom", iso.ISOFile) - } - _, err := client.SetVmConfig(vmRef, addISOConfig) - if err != nil { - err := fmt.Errorf("Error updating template: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - // The EFI disk doesn't get created reliably when using the clone builder, // so let's make sure it's there. if c.EFIConfig != (efiConfig{}) && c.Ctx.BuildType == "proxmox-clone" { @@ -280,17 +277,122 @@ func generateProxmoxNetworkAdapters(nics []NICConfig) proxmox.QemuDevices { return devs } -func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { +func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisks []string) (*packersdk.MultiError, []string, *proxmox.QemuStorages) { ideDisks := proxmox.QemuIdeDisks{} sataDisks := proxmox.QemuSataDisks{} scsiDisks := proxmox.QemuScsiDisks{} virtIODisks := proxmox.QemuVirtIODisks{} + qemuStorages := proxmox.QemuStorages{ + Ide: &ideDisks, + Sata: &sataDisks, + Scsi: &scsiDisks, + VirtIO: &virtIODisks, + } ideCount := 0 sataCount := 0 scsiCount := 0 virtIOCount := 0 + var errs *packersdk.MultiError + var warnings []string + + // Versions up to 1.8 supported static assignment of ISOs to a bus index, however hard disks did not support static bus indexes. + // For backwards compatibility handle statically mapped ISOs first (guarantee allocation) then allocate hard disks and remaining ISOs in free slots around them. + + // Map ISOs with a static index assignment + if len(isos) > 0 { + for idx := range isos { + // if a static assignment has been defined + if isos[idx].Index != "" { + // IsoFile struct parses the ISO File and Storage Pool as separate fields. + isoFile := strings.Split(isos[idx].ISOFile, ":iso/") + + // define QemuCdRom containing isoFile properties + cdrom := &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: isoFile[1], + Storage: isoFile[0], + }, + } + + switch isos[idx].Type { + case "ide": + dev := proxmox.QemuIdeStorage{ + CdRom: cdrom, + } + deviceIndex := fmt.Sprintf("ide%s", isos[idx].Index) + log.Printf("Mapping static assigned ISO to %s", deviceIndex) + if slices.Contains(cloneSourceDisks, deviceIndex) { + // Backwards compatibility: statically assigned ISOs overwrote assignments of existing disks when using the clone builder + // issue a warning so users are aware and can decide if they want to remap the ISO device + warnings = append(warnings, fmt.Sprintf("an existing hard disk was found at %s on the clone source VM, overwriting with ISO configured for the same address", deviceIndex)) + } + // We need reflection here as the storage objects are not exposed + // as a slice, but as a series of named fields in the structure + // that the APIs use. + // + // This means that assigning the disks in the order they're defined + // in would result in a bunch of `switch` cases for the index, and + // named field assignation for each. + // + // Example: + // ``` + // switch ideCount { + // case 0: + // dev.Disk_0 = dev + // case 1: + // dev.Disk_1 = dev + // [...] + // } + // ``` + // + // Instead, we use reflection to address the fields algorithmically, + // so we don't need to write this verbose code. + reflect. + // We need to get the pointer to the structure so we can + // assign a value to the disk + ValueOf(&ideDisks).Elem(). + // Get the field from its name, each disk's field has a + // similar format 'Disk_%d' + FieldByName(fmt.Sprintf("Disk_%s", isos[idx].Index)). + // Assign dev to the Disk_%d field + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = deviceIndex + case "sata": + dev := proxmox.QemuSataStorage{ + CdRom: cdrom, + } + deviceIndex := fmt.Sprintf("sata%s", isos[idx].Index) + log.Printf("Mapping static assigned ISO to %s", deviceIndex) + if slices.Contains(cloneSourceDisks, deviceIndex) { + warnings = append(warnings, fmt.Sprintf("an existing hard disk was found at %s on the clone source VM, overwriting with ISO configured for the same address", deviceIndex)) + } + reflect. + ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%s", isos[idx].Index)). + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = deviceIndex + case "scsi": + dev := proxmox.QemuScsiStorage{ + CdRom: cdrom, + } + deviceIndex := fmt.Sprintf("scsi%s", isos[idx].Index) + log.Printf("Mapping static assigned ISO to %s", deviceIndex) + if slices.Contains(cloneSourceDisks, fmt.Sprintf("scsi%d", scsiCount)) { + warnings = append(warnings, fmt.Sprintf("an existing hard disk was found at %s on the clone source VM, overwriting with ISO configured for the same address", deviceIndex)) + } + reflect. + ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%s", isos[idx].Index)). + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = deviceIndex + } + } + } + } + + // Map Hard Disks for idx := range disks { tmpSize, _ := strconv.ParseInt(disks[idx].Size[:len(disks[idx].Size)-1], 10, 0) size := proxmox.QemuDiskSize(0) @@ -304,6 +406,10 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { case "K": size = proxmox.QemuDiskSize(tmpSize) } + backup := true + if disks[idx].ExcludeFromBackup { + backup = false + } switch disks[idx].Type { case "ide": @@ -311,96 +417,241 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { Disk: &proxmox.QemuIdeDisk{ SizeInKibibytes: size, Storage: disks[idx].StoragePool, + AsyncIO: proxmox.QemuDiskAsyncIO(disks[idx].AsyncIO), Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, + Backup: backup, }, } - // We need reflection here as the storage objects are not exposed - // as a slice, but as a series of named fields in the structure - // that the APIs use. - // - // This means that assigning the disks in the order they're defined - // in would result in a bunch of `switch` cases for the index, and - // named field assignation for each. - // - // Example: - // ``` - // switch ideCount { - // case 0: - // dev.Disk_0 = dev - // case 1: - // dev.Disk_1 = dev - // [...] - // } - // ``` - // - // Instead, we use reflection to address the fields algorithmically, - // so we don't need to write this verbose code. - reflect. - // We need to get the pointer to the structure so we can - // assign a value to the disk - ValueOf(&ideDisks).Elem(). - // Get the field from its name, each disk's field has a - // similar format 'Disk_%d' - FieldByName(fmt.Sprintf("Disk_%d", ideCount)). - Set(reflect.ValueOf(&dev)) - ideCount++ + for { + log.Printf("Mapping Disk to ide%d", ideCount) + // If IDE has too many devices attached pass an error then exit the loop + if ideCount > 3 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached ide index %d, too many ide devices configured. Ensure total Disk and ISO ide assignments don't exceed 4 devices", ideCount)) + break + } + + // If this ide device index isn't occupied by a disk on a clone builder source vm + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("ide%d", ideCount)) && + // or occupied by a statically mapped ISO + reflect. + ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", ideCount)). + IsNil() { + reflect. + ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", ideCount)). + Set(reflect.ValueOf(&dev)) + ideCount++ + break + } + // if the disk field is not empty (occupied by an ISO), try the next index + log.Printf("ide%d occupied, trying next device index", ideCount) + ideCount++ + } case "scsi": dev := proxmox.QemuScsiStorage{ Disk: &proxmox.QemuScsiDisk{ SizeInKibibytes: size, Storage: disks[idx].StoragePool, + AsyncIO: proxmox.QemuDiskAsyncIO(disks[idx].AsyncIO), Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, IOThread: disks[idx].IOThread, + Backup: backup, }, } - reflect.ValueOf(&scsiDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). - Set(reflect.ValueOf(&dev)) - scsiCount++ + for { + log.Printf("Mapping Disk to scsi%d", scsiCount) + if scsiCount > 30 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached scsi index %d, too many scsi devices configured. Ensure total disk and ISO scsi assignments don't exceed 31 devices", scsiCount)) + break + } + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("scsi%d", scsiCount)) && + reflect.ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + IsNil() { + reflect. + ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + Set(reflect.ValueOf(&dev)) + scsiCount++ + break + } + log.Printf("scsi%d occupied, trying next device index", scsiCount) + scsiCount++ + } case "sata": dev := proxmox.QemuSataStorage{ Disk: &proxmox.QemuSataDisk{ SizeInKibibytes: size, Storage: disks[idx].StoragePool, + AsyncIO: proxmox.QemuDiskAsyncIO(disks[idx].AsyncIO), Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, + Backup: backup, }, } - reflect.ValueOf(&sataDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", sataCount)). - Set(reflect.ValueOf(&dev)) - sataCount++ + for { + if sataCount > 5 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached sata index %d, too many sata devices configured. Ensure total disk and ISO sata assignments don't exceed 6 devices", sataCount)) + break + } + log.Printf("Mapping Disk to sata%d", sataCount) + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("sata%d", sataCount)) && + reflect.ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + IsNil() { + reflect. + ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + Set(reflect.ValueOf(&dev)) + sataCount++ + break + } + log.Printf("sata%d occupied, trying next device index", sataCount) + sataCount++ + } case "virtio": dev := proxmox.QemuVirtIOStorage{ Disk: &proxmox.QemuVirtIODisk{ SizeInKibibytes: size, Storage: disks[idx].StoragePool, + AsyncIO: proxmox.QemuDiskAsyncIO(disks[idx].AsyncIO), Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, IOThread: disks[idx].IOThread, + Backup: backup, }, } - reflect.ValueOf(&virtIODisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). - Set(reflect.ValueOf(&dev)) - virtIOCount++ + for { + log.Printf("Mapping Disk to virtio%d", virtIOCount) + if virtIOCount > 15 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("enumeration reached virtio index %d, too many virtio devices configured. Ensure total disk and ISO virtio assignments don't exceed 16 devices", virtIOCount)) + break + } + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("virtio%d", virtIOCount)) && + reflect.ValueOf(&virtIODisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). + IsNil() { + reflect. + ValueOf(&virtIODisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). + Set(reflect.ValueOf(&dev)) + virtIOCount++ + break + } + log.Printf("virtio%d occupied, trying next device index", virtIOCount) + virtIOCount++ + } } } - return &proxmox.QemuStorages{ - Ide: &ideDisks, - Sata: &sataDisks, - Scsi: &scsiDisks, - VirtIO: &virtIODisks, + + // Map ISOs without static mappings in remaining device indexes + if len(isos) > 0 { + for idx := range isos { + // if a static assignment has not been defined + if isos[idx].Index == "" { + // IsoFile struct parses the ISO File and Storage Pool as separate fields. + isoFile := strings.Split(isos[idx].ISOFile, ":iso/") + + // define QemuCdRom containing isoFile properties + cdrom := &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: isoFile[1], + Storage: isoFile[0], + }, + } + + switch isos[idx].Type { + case "ide": + dev := proxmox.QemuIdeStorage{ + CdRom: cdrom, + } + for { + log.Printf("Mapping ISO to ide%d", ideCount) + if ideCount > 3 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached ide index %d, too many ide devices configured. Ensure total Disk and ISO ide assignments don't exceed 4 devices", ideCount)) + break + } + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("ide%d", ideCount)) && + reflect.ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", ideCount)). + IsNil() { + reflect. + ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", ideCount)). + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = fmt.Sprintf("ide%d", ideCount) + ideCount++ + break + } + log.Printf("ide%d occupied, trying next device index", ideCount) + ideCount++ + } + case "sata": + dev := proxmox.QemuSataStorage{ + CdRom: cdrom, + } + for { + if sataCount > 5 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached sata index %d, too many sata devices configured. Ensure total disk and ISO sata assignments don't exceed 6 devices", sataCount)) + break + } + log.Printf("Mapping ISO to sata%d", sataCount) + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("sata%d", sataCount)) && + reflect.ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + IsNil() { + reflect. + ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = fmt.Sprintf("sata%d", sataCount) + sataCount++ + break + } + log.Printf("sata%d occupied, trying next device index", sataCount) + sataCount++ + } + case "scsi": + dev := proxmox.QemuScsiStorage{ + CdRom: cdrom, + } + for { + log.Printf("Mapping ISO to scsi%d", scsiCount) + if scsiCount > 30 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("storage enumeration reached scsi index %d, too many scsi devices configured. Ensure total disk and ISO scsi assignments don't exceed 31 devices", scsiCount)) + break + } + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("scsi%d", scsiCount)) && + reflect.ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + IsNil() { + reflect. + ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + Set(reflect.ValueOf(&dev)) + isos[idx].AssignedDeviceIndex = fmt.Sprintf("scsi%d", scsiCount) + scsiCount++ + break + } + log.Printf("scsi%d occupied, trying next device index", scsiCount) + scsiCount++ + } + } + } + } } + + return errs, warnings, &qemuStorages } func generateProxmoxPCIDeviceMap(devices []pciDeviceConfig) proxmox.QemuDevices { diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 7ca87200..a228778b 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -501,24 +501,31 @@ func TestStartVM_AssertInitialQuemuConfig(t *testing.T) { func TestGenerateProxmoxDisks(t *testing.T) { tests := []struct { - name string - disks []diskConfig - expectOutput *proxmox.QemuStorages + name string + disks []diskConfig + isos []ISOsConfig + clonesourcedisks []string + expectedToFail bool + expectOutput *proxmox.QemuStorages }{ { "plain config, no special option set", []diskConfig{ { - Type: "scsi", - StoragePool: "local-lvm", - Size: "10G", - CacheMode: "none", - DiskFormat: "qcow2", - IOThread: false, - Discard: false, - SSD: false, + Type: "scsi", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: false, + Discard: false, + SSD: false, + ExcludeFromBackup: false, }, }, + []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -527,11 +534,13 @@ func TestGenerateProxmoxDisks(t *testing.T) { Disk: &proxmox.QemuScsiDisk{ SizeInKibibytes: 10485760, Storage: "local-lvm", + AsyncIO: proxmox.QemuDiskAsyncIO(""), Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, EmulateSSD: false, IOThread: false, + Backup: true, }, }, }, @@ -542,16 +551,20 @@ func TestGenerateProxmoxDisks(t *testing.T) { "scsi + iothread, iothread should be true", []diskConfig{ { - Type: "scsi", - StoragePool: "local-lvm", - Size: "10G", - CacheMode: "none", - DiskFormat: "qcow2", - IOThread: true, - Discard: false, - SSD: false, + Type: "scsi", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + Discard: false, + SSD: false, + ExcludeFromBackup: false, }, }, + []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -560,11 +573,13 @@ func TestGenerateProxmoxDisks(t *testing.T) { Disk: &proxmox.QemuScsiDisk{ SizeInKibibytes: 10485760, Storage: "local-lvm", + AsyncIO: proxmox.QemuDiskAsyncIO(""), Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, EmulateSSD: false, IOThread: true, + Backup: true, }, }, }, @@ -575,16 +590,97 @@ func TestGenerateProxmoxDisks(t *testing.T) { "virtio + iothread, iothread should be true", []diskConfig{ { - Type: "virtio", - StoragePool: "local-lvm", - Size: "10G", - CacheMode: "none", - DiskFormat: "qcow2", - IOThread: true, - Discard: false, - SSD: false, + Type: "virtio", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + Discard: false, + SSD: false, + ExcludeFromBackup: false, + }, + }, + []ISOsConfig{}, + []string{}, + false, + &proxmox.QemuStorages{ + Ide: &proxmox.QemuIdeDisks{}, + Sata: &proxmox.QemuSataDisks{}, + Scsi: &proxmox.QemuScsiDisks{}, + VirtIO: &proxmox.QemuVirtIODisks{ + Disk_0: &proxmox.QemuVirtIOStorage{ + Disk: &proxmox.QemuVirtIODisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + AsyncIO: proxmox.QemuDiskAsyncIO(""), + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + Backup: true, + }, + }, + }, + }, + }, + { + "asyncio is native", + []diskConfig{ + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + AsyncIO: "native", + IOThread: true, + Discard: false, + SSD: false, + ExcludeFromBackup: false, + }, + }, + []ISOsConfig{}, + []string{}, + false, + &proxmox.QemuStorages{ + Ide: &proxmox.QemuIdeDisks{}, + Sata: &proxmox.QemuSataDisks{}, + Scsi: &proxmox.QemuScsiDisks{}, + VirtIO: &proxmox.QemuVirtIODisks{ + Disk_0: &proxmox.QemuVirtIOStorage{ + Disk: &proxmox.QemuVirtIODisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + AsyncIO: proxmox.QemuDiskAsyncIO("native"), + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + Backup: true, + }, + }, + }, + }, + }, + { + "exclude disk from backup", + []diskConfig{ + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + Discard: false, + SSD: false, + ExcludeFromBackup: true, }, }, + []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -598,11 +694,296 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: false, }, }, }, }, }, + { + "overallocate ide, should error", + []diskConfig{ + { + Type: "ide", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + []ISOsConfig{}, + []string{ + "ide0", + "ide1", + "ide2", + "ide3", + }, + true, + &proxmox.QemuStorages{}, + }, + { + "overallocate sata, should error", + []diskConfig{ + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + []ISOsConfig{ + { + Type: "sata", + ISOFile: "local:iso/test.iso", + }, + }, + []string{ + "sata0", + }, + true, + &proxmox.QemuStorages{}, + }, + { + "overallocate scsi, should error", + []diskConfig{ + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + []ISOsConfig{ + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + { + Type: "scsi", + ISOFile: "local:iso/test.iso", + }, + }, + []string{ + "scsi0", + "scsi1", + "scsi2", + "scsi3", + "scsi4", + "scsi5", + "scsi6", + "scsi7", + "scsi8", + "scsi9", + "scsi10", + "scsi11", + }, + true, + &proxmox.QemuStorages{}, + }, + { + "overallocate virtio, should error", + []diskConfig{ + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + []ISOsConfig{}, + []string{ + "virtio0", + "virtio1", + "virtio2", + "virtio3", + "virtio4", + "virtio5", + "virtio6", + "virtio7", + "virtio8", + "virtio9", + "virtio10", + "virtio11", + "virtio12", + }, + true, + &proxmox.QemuStorages{}, + }, { "bunch of disks, should be defined in the discovery order", []diskConfig{ @@ -671,6 +1052,9 @@ func TestGenerateProxmoxDisks(t *testing.T) { IOThread: true, }, }, + []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -680,6 +1064,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuIdeStorage{ @@ -689,6 +1074,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, }, @@ -700,6 +1086,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuSataStorage{ @@ -709,6 +1096,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, }, @@ -721,6 +1109,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, Disk_1: &proxmox.QemuScsiStorage{ @@ -731,6 +1120,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, @@ -743,6 +1133,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, Disk_1: &proxmox.QemuVirtIOStorage{ @@ -753,17 +1144,168 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, }, }, + { + "bunch of disks, Additional ISOs", + []diskConfig{ + { + Type: "ide", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "11G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "ide", + StoragePool: "local-lvm", + Size: "12G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "ide", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "sata", + StoragePool: "local-lvm", + Size: "13G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "14G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + []ISOsConfig{ + { + Type: "sata", + ISOFile: "local:iso/test.iso", + }, + }, + []string{}, + false, + &proxmox.QemuStorages{ + Ide: &proxmox.QemuIdeDisks{ + Disk_0: &proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + Backup: true, + }, + }, + Disk_1: &proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: 12582912, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + Backup: true, + }, + }, + Disk_2: &proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + Backup: true, + }, + }, + }, + Sata: &proxmox.QemuSataDisks{ + Disk_0: &proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: 11534336, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + Backup: true, + }, + }, + Disk_1: &proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: 13631488, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + Backup: true, + }, + }, + Disk_2: &proxmox.QemuSataStorage{ + CdRom: &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: "test.iso", + Storage: "local", + }, + }, + }, + }, + Scsi: &proxmox.QemuScsiDisks{ + Disk_0: &proxmox.QemuScsiStorage{ + Disk: &proxmox.QemuScsiDisk{ + SizeInKibibytes: 14680064, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + Backup: true, + }, + }, + }, + VirtIO: &proxmox.QemuVirtIODisks{}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - devs := generateProxmoxDisks(tt.disks) - assert.Equal(t, devs, tt.expectOutput) + err, _, devs := generateProxmoxDisks(tt.disks, tt.isos, tt.clonesourcedisks) + + if tt.expectedToFail && err == nil { + t.Error("expected config preparation to fail, but no error occured") + } + + if !tt.expectedToFail && err != nil { + t.Errorf("expected config preparation to succeed, but %s", err.Error()) + } + + if !tt.expectedToFail { + assert.Equal(t, devs, tt.expectOutput) + } }) } } diff --git a/builder/proxmox/common/step_upload_additional_iso.go b/builder/proxmox/common/step_upload_iso.go similarity index 90% rename from builder/proxmox/common/step_upload_additional_iso.go rename to builder/proxmox/common/step_upload_iso.go index 5b2ba851..98f6de33 100644 --- a/builder/proxmox/common/step_upload_additional_iso.go +++ b/builder/proxmox/common/step_upload_iso.go @@ -15,9 +15,9 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) -// stepUploadAdditionalISO uploads an ISO file -type stepUploadAdditionalISO struct { - ISO *additionalISOsConfig +// stepUploadISO uploads an ISO file +type stepUploadISO struct { + ISO *ISOsConfig } type uploader interface { @@ -27,7 +27,7 @@ type uploader interface { var _ uploader = &proxmoxapi.Client{} -func (s *stepUploadAdditionalISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { +func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packersdk.Ui) client := state.Get("proxmoxClient").(uploader) c := state.Get("config").(*Config) @@ -86,7 +86,7 @@ func (s *stepUploadAdditionalISO) Run(ctx context.Context, state multistep.State return multistep.ActionContinue } -func (s *stepUploadAdditionalISO) Cleanup(state multistep.StateBag) { +func (s *stepUploadISO) Cleanup(state multistep.StateBag) { c := state.Get("config").(*Config) ui := state.Get("ui").(packersdk.Ui) client := state.Get("proxmoxClient").(uploader) diff --git a/builder/proxmox/common/step_upload_additional_iso_test.go b/builder/proxmox/common/step_upload_iso_test.go similarity index 90% rename from builder/proxmox/common/step_upload_additional_iso_test.go rename to builder/proxmox/common/step_upload_iso_test.go index b1a409dc..0bd7e43b 100644 --- a/builder/proxmox/common/step_upload_additional_iso_test.go +++ b/builder/proxmox/common/step_upload_iso_test.go @@ -40,11 +40,11 @@ func (m *uploaderMock) DeleteVolume(vmr *proxmox.VmRef, storageName string, volu var _ uploader = &uploaderMock{} -func TestUploadAdditionalISO(t *testing.T) { +func TestUploadISO(t *testing.T) { cs := []struct { name string builderConfig *Config - step *stepUploadAdditionalISO + step *stepUploadISO testAssert func(m *uploaderMock, action multistep.StepAction) downloadPath string generatedISOPath string @@ -59,8 +59,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "should not call upload unless configured to do so", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: false, }, }, @@ -70,8 +70,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "StepCreateCD not called (no cd_path present) should halt", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, CDConfig: commonsteps.CDConfig{ CDFiles: []string{"testfile"}, @@ -84,8 +84,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "DownloadPathKey not valid should halt", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, DownloadPathKey: "", }, @@ -96,8 +96,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "ISO not found should halt", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, DownloadPathKey: "filethatdoesnotexist.iso", }, @@ -109,8 +109,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "generated ISO should be uploaded and deleted", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, ISOStoragePool: "local", CDConfig: commonsteps.CDConfig{ @@ -130,8 +130,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "generated ISO should be uploaded but deletion failed", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, ISOStoragePool: "local", CDConfig: commonsteps.CDConfig{ @@ -151,8 +151,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "downloaded ISO should be uploaded", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, ISOStoragePool: "local", DownloadPathKey: "../iso/testdata/test.iso", @@ -169,8 +169,8 @@ func TestUploadAdditionalISO(t *testing.T) { { name: "downloaded ISO fail upload", builderConfig: &Config{}, - step: &stepUploadAdditionalISO{ - ISO: &additionalISOsConfig{ + step: &stepUploadISO{ + ISO: &ISOsConfig{ ShouldUploadISO: true, ISOStoragePool: "local", DownloadPathKey: "../iso/testdata/test.iso", diff --git a/builder/proxmox/iso/builder.go b/builder/proxmox/iso/builder.go index cafdb7e6..83906f60 100644 --- a/builder/proxmox/iso/builder.go +++ b/builder/proxmox/iso/builder.go @@ -5,13 +5,11 @@ package proxmoxiso import ( "context" - "strings" proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" "github.com/hashicorp/hcl/v2/hcldec" proxmox "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" "github.com/hashicorp/packer-plugin-sdk/multistep" - "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) @@ -31,38 +29,19 @@ 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 packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) { state := new(multistep.BasicStateBag) + + // prepend boot iso device to any defined additional_isos + var isoArray []proxmox.ISOsConfig + isoArray = append(isoArray, b.config.BootISO) + isoArray = append(isoArray, b.config.ISOs...) + b.config.ISOs = isoArray + state.Put("iso-config", &b.config) preSteps := []multistep.Step{} - if b.config.ISODownloadPVE { - preSteps = append(preSteps, - &stepDownloadISOOnPVE{ - ISOStoragePool: b.config.ISOStoragePool, - ISOUrls: b.config.ISOUrls, - ISOChecksum: b.config.ISOChecksum, - }, - ) - } else { - preSteps = append(preSteps, - &commonsteps.StepDownload{ - Checksum: b.config.ISOChecksum, - Description: "ISO", - Extension: b.config.TargetExtension, - ResultKey: downloadPathKey, - TargetPath: b.config.TargetPath, - Url: b.config.ISOUrls, - }, - &stepUploadISO{}, - ) - } - - postSteps := []multistep.Step{ - &stepFinalizeISOTemplate{}, - } + postSteps := []multistep.Step{} sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps, &isoVMCreator{}) return sb.Run(ctx, ui, hook, state) @@ -71,12 +50,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) type isoVMCreator struct{} func (*isoVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error { - isoFile := strings.Split(state.Get("iso_file").(string), ":iso/") - config.Iso = &proxmoxapi.IsoFile{ - File: isoFile[1], - Storage: isoFile[0], - } - client := state.Get("proxmoxClient").(*proxmoxapi.Client) return config.Create(vmRef, client) } diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go index b9772392..7bd610f3 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -2,38 +2,71 @@ // SPDX-License-Identifier: MPL-2.0 //go:generate packer-sdc struct-markdown -//go:generate packer-sdc mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,additionalISOsConfig +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,ISOsConfig package proxmoxiso import ( "errors" + "fmt" + "log" - proxmoxcommon "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" + common "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) type Config struct { - proxmoxcommon.Config `mapstructure:",squash"` - + common.Config `mapstructure:",squash"` + // No longer required when deprecated boot iso options are removed commonsteps.ISOConfig `mapstructure:",squash"` + // DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. // Path to the ISO file to boot from, expressed as a // proxmox datastore path, for example // `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. // Either `iso_file` OR `iso_url` must be specifed. ISOFile string `mapstructure:"iso_file"` + // DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. // Proxmox storage pool onto which to upload // the ISO file. ISOStoragePool string `mapstructure:"iso_storage_pool"` + // DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. // Download the ISO directly from the PVE node rather than through Packer. // // Defaults to `false` ISODownloadPVE bool `mapstructure:"iso_download_pve"` + // DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. // If true, remove the mounted ISO from the template // after finishing. Defaults to `false`. - UnmountISO bool `mapstructure:"unmount_iso"` - shouldUploadISO bool + UnmountISO bool `mapstructure:"unmount_iso"` + // Boot ISO attached to the virtual machine. + // + // JSON Example: + // + // ```json + // + // "boot_iso": { + // "type": "scsi", + // "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + // "unmount": true, + // "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + // } + // + // ``` + // HCL2 example: + // + // ```hcl + // + // boot_iso { + // type = "scsi" + // iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" + // unmount = true + // iso_checksum = "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + // } + // + // ``` + // See [ISOs](#isos) for additional options. + BootISO common.ISOsConfig `mapstructure:"boot_iso" required:"true"` } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { @@ -43,26 +76,98 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, merrs) } - // Check ISO config + // Convert deprecated config options + if c.ISOFile != "" { + warnings = append(warnings, "'iso_file' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + // Convert this field across to c.BootISO struct + c.BootISO.ISOFile = c.ISOFile + } + if c.ISOStoragePool != "" { + warnings = append(warnings, "'iso_storage_pool' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.ISOStoragePool = c.ISOStoragePool + } + if c.ISODownloadPVE { + warnings = append(warnings, "'iso_download_pve' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.ISODownloadPVE = c.ISODownloadPVE + } + if len(c.ISOUrls) > 0 { + warnings = append(warnings, "'iso_urls' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.ISOUrls = c.ISOUrls + } + if c.RawSingleISOUrl != "" { + warnings = append(warnings, "'iso_url' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.RawSingleISOUrl = c.RawSingleISOUrl + } + if c.ISOChecksum != "" { + warnings = append(warnings, "'iso_checksum' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.ISOChecksum = c.ISOChecksum + } + if c.UnmountISO { + warnings = append(warnings, "'unmount_iso' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.Unmount = c.UnmountISO + } + if c.TargetPath != "" { + warnings = append(warnings, "'iso_target_path' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.TargetPath = c.TargetPath + } + if c.TargetExtension != "" { + warnings = append(warnings, "'iso_target_extension' is deprecated and will be removed in a future release, define the boot iso options in a 'boot_iso' block") + c.BootISO.TargetExtension = c.TargetExtension + } + + // Check Boot 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 iso_download_pve is true, iso_url will be downloaded directly to the - // PVE node. - if c.ISOFile != "" { - c.shouldUploadISO = false + if c.BootISO.ISOFile != "" { + c.BootISO.ShouldUploadISO = false } else { - isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.Ctx) - errs = packersdk.MultiErrorAppend(errs, isoErrors...) - warnings = append(warnings, isoWarnings...) - c.shouldUploadISO = true + c.BootISO.DownloadPathKey = "downloaded_iso_path" + if len(c.BootISO.CDFiles) > 0 || len(c.BootISO.CDContent) > 0 { + cdErrors := c.BootISO.CDConfig.Prepare(&c.Ctx) + errs = packersdk.MultiErrorAppend(errs, cdErrors...) + } else { + isoWarnings, isoErrors := c.BootISO.ISOConfig.Prepare(&c.Ctx) + errs = packersdk.MultiErrorAppend(errs, isoErrors...) + warnings = append(warnings, isoWarnings...) + } + c.BootISO.ShouldUploadISO = true } - - if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) { - errs = packersdk.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified")) + // validate device type, assign if unset + // For backwards compatibility <= v1.8, set ide2 as default if not configured + switch c.BootISO.Type { + case "ide", "sata", "scsi": + case "": + log.Print("boot_iso device type not set, using default type 'ide' and index '2'") + c.BootISO.Type = "ide" + c.BootISO.Index = "2" + default: + errs = packersdk.MultiErrorAppend(errs, errors.New("ISOs must be of type ide, sata or scsi. VirtIO not supported by Proxmox for ISO devices")) + } + if len(c.BootISO.CDFiles) > 0 || len(c.BootISO.CDContent) > 0 { + if c.BootISO.ISOStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("boot_iso storage_pool not set for storage of generated ISO from cd_files or cd_content")) + } + } + if len(c.BootISO.ISOUrls) != 0 && c.BootISO.ISOStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url in a boot_iso block, iso_storage_pool must also be specified")) + } + // Check only one option is present + options := 0 + if c.BootISO.ISOFile != "" { + options++ + } + if len(c.BootISO.ISOConfig.ISOUrls) > 0 || c.BootISO.ISOConfig.RawSingleISOUrl != "" { + options++ + } + if len(c.BootISO.CDFiles) > 0 || len(c.BootISO.CDContent) > 0 { + options++ + } + if options != 1 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("one of iso_file, iso_url, or a combination of cd_files and cd_content must be specified for boot_iso")) } - if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified")) + if len(c.BootISO.ISOConfig.ISOUrls) == 0 && c.BootISO.ISOConfig.RawSingleISOUrl == "" && c.BootISO.ISODownloadPVE { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("iso_download_pve can only be used together with iso_url")) } if errs != nil && len(errs.Errors) > 0 { diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index cea97b57..3e77a919 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -11,124 +11,125 @@ import ( // 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"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - 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"` - HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` - HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` - HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` - HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` - HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` - BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` - BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` - BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` - BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` - Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` - SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` - SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` - 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"` - Token *string `mapstructure:"token" cty:"token" hcl:"token"` - Node *string `mapstructure:"node" cty:"node" hcl:"node"` - Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` - TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` - VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` - VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` - Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` - Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` - Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` - BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` - 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"` - Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` - OS *string `mapstructure:"os" cty:"os" hcl:"os"` - BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` - EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` - EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` - Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` - Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` - TPMConfig *proxmox.FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` - 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"` - PCIDevices []proxmox.FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` - Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` - 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"` - CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` - AdditionalISOFiles []proxmox.FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` - VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` - AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` - ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` - RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` - ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` - TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` - TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` - ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` - ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` - ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` - UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"` + 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"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + 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"` + HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"` + HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` + HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` + BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` + Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` + SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"` + SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"` + 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"` + Token *string `mapstructure:"token" cty:"token" hcl:"token"` + Node *string `mapstructure:"node" cty:"node" hcl:"node"` + Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` + TaskTimeout *string `mapstructure:"task_timeout" cty:"task_timeout" hcl:"task_timeout"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` + Tags *string `mapstructure:"tags" cty:"tags" hcl:"tags"` + Boot *string `mapstructure:"boot" cty:"boot" hcl:"boot"` + Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` + BalloonMinimum *int `mapstructure:"ballooning_minimum" cty:"ballooning_minimum" hcl:"ballooning_minimum"` + 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"` + Numa *bool `mapstructure:"numa" cty:"numa" hcl:"numa"` + OS *string `mapstructure:"os" cty:"os" hcl:"os"` + BIOS *string `mapstructure:"bios" cty:"bios" hcl:"bios"` + EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` + EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` + Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` + TPMConfig *proxmox.FlattpmConfig `mapstructure:"tpm_config" cty:"tpm_config" hcl:"tpm_config"` + 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"` + PCIDevices []proxmox.FlatpciDeviceConfig `mapstructure:"pci_devices" cty:"pci_devices" hcl:"pci_devices"` + Serials []string `mapstructure:"serials" cty:"serials" hcl:"serials"` + 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"` + CloudInitDiskType *string `mapstructure:"cloud_init_disk_type" cty:"cloud_init_disk_type" hcl:"cloud_init_disk_type"` + ISOs []proxmox.FlatISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` + VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` + AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` + ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` + ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` + UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"` + BootISO *proxmox.FlatISOsConfig `mapstructure:"boot_iso" required:"true" cty:"boot_iso" hcl:"boot_iso"` } // FlatMapstructure returns a new FlatConfig. @@ -249,7 +250,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, "cloud_init_disk_type": &hcldec.AttrSpec{Name: "cloud_init_disk_type", Type: cty.String, Required: false}, - "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatadditionalISOsConfig)(nil).HCL2Spec())}, + "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", Type: cty.String, Required: false}, "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, @@ -261,6 +262,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false}, "iso_download_pve": &hcldec.AttrSpec{Name: "iso_download_pve", Type: cty.Bool, Required: false}, "unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false}, + "boot_iso": &hcldec.BlockSpec{TypeName: "boot_iso", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(nil).HCL2Spec())}, } return s } diff --git a/builder/proxmox/iso/config_test.go b/builder/proxmox/iso/config_test.go index 633da44b..f2540087 100644 --- a/builder/proxmox/iso/config_test.go +++ b/builder/proxmox/iso/config_test.go @@ -34,8 +34,12 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { "storage_pool_type": "lvm" } ], - - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "boot_iso": { + "type": "sata", + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "iso_storage_pool": "local-lvm", + "unmount": "true" + }, "http_directory":"config", "boot_wait": "10s", "boot_command": [ @@ -46,7 +50,6 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { "ssh_timeout": "15m", "ssh_password": "packer", - "unmount_iso": true, "template_name": "fedora-29", "template_description": "Fedora 29-1.2, generated on {{ isotime \"2006-01-02T15:04:05Z\" }}" } @@ -114,6 +117,58 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { } } +func TestDeprecatedBootISOOptionsAreConverted(t *testing.T) { + const config = `{ + "builders": [ + { + "type": "proxmox-iso", + "proxmox_url": "https://my-proxmox.my-domain:8006/api2/json", + "insecure_skip_tls_verify": true, + "username": "apiuser@pve", + "password": "supersecret", + "node": "my-proxmox", + + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "unmount_iso": true, + "iso_storage_pool": "local", + "iso_target_path": "./test", + "iso_target_extension": "img", + + "ssh_username": "root", + "ssh_password": "packer" + } + ] +}` + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatal(err) + } + + b := &Builder{} + _, _, err = b.Prepare(tpl.Builders["proxmox-iso"].Config) + if err != nil { + t.Fatal(err) + } + + // Validate that each deprecated boot ISO option is converted over to the iso struct + + if b.config.BootISO.ISOFile != "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" { + t.Errorf("Expected iso_file to be converted to boot_iso.iso_file: local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso, got %s", b.config.BootISO.ISOFile) + } + if !b.config.BootISO.Unmount { + t.Errorf("Expected unmount_iso to be converted to boot_iso.unmount: true, got %t", b.config.BootISO.Unmount) + } + if b.config.BootISO.ISOStoragePool != "local" { + t.Errorf("Expected iso_storage_pool to be converted to boot_iso.iso_storage_pool: local, got %s", b.config.BootISO.ISOStoragePool) + } + if b.config.BootISO.TargetExtension != "img" { + t.Errorf("Expected iso_target_extension to be converted to boot_iso.iso_target_extension: img, got %s", b.config.BootISO.TargetExtension) + } + if b.config.BootISO.TargetPath != "./test" { + t.Errorf("Expected iso_target_path to be converted to boot_iso.iso_target_path: ./test, got %s", b.config.BootISO.TargetExtension) + } +} + func TestAgentSetToFalse(t *testing.T) { cfg := mandatoryConfig(t) cfg["qemu_agent"] = false @@ -230,6 +285,11 @@ func mandatoryConfig(t *testing.T) map[string]interface{} { "password": "supersecret", "node": "my-proxmox", "ssh_username": "root", - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "boot_iso": map[string]interface{}{ + "type": "sata", + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "iso_storage_pool": "local-lvm", + "unmount": "true", + }, } } diff --git a/builder/proxmox/iso/step_download_iso_on_pve.go b/builder/proxmox/iso/step_download_iso_on_pve.go deleted file mode 100644 index 99cb646f..00000000 --- a/builder/proxmox/iso/step_download_iso_on_pve.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxmoxiso - -import ( - "context" - - proxmoxcommon "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" - "github.com/hashicorp/packer-plugin-sdk/multistep" -) - -// stepDownloadISOOnPVE downloads an ISO file directly to the specified PVE node. -// Checksums are also calculated and compared on the PVE node, not by Packer. -type stepDownloadISOOnPVE struct { - ISOStoragePool string - ISOUrls []string - ISOChecksum string -} - -func (s *stepDownloadISOOnPVE) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - builderConfig := state.Get("iso-config").(*Config) - if !builderConfig.shouldUploadISO { - state.Put("iso_file", builderConfig.ISOFile) - return multistep.ActionContinue - } - - var isoStoragePath string - isoStoragePath, err := proxmoxcommon.DownloadISOOnPVE(state, s.ISOUrls, s.ISOChecksum, s.ISOStoragePool) - - // Abort if no ISO can be downloaded - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - // If available, set the file path to the downloaded iso file on the node - state.Put("iso_file", isoStoragePath) - return multistep.ActionContinue -} - -func (s *stepDownloadISOOnPVE) Cleanup(state multistep.StateBag) { -} diff --git a/builder/proxmox/iso/step_finalize_iso.go b/builder/proxmox/iso/step_finalize_iso.go deleted file mode 100644 index 21f16ee9..00000000 --- a/builder/proxmox/iso/step_finalize_iso.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxmoxiso - -import ( - "context" - "fmt" - "strings" - - "github.com/Telmate/proxmox-api-go/proxmox" - "github.com/hashicorp/packer-plugin-sdk/multistep" - packersdk "github.com/hashicorp/packer-plugin-sdk/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").(packersdk.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.Contains(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(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 deleted file mode 100644 index 82d37493..00000000 --- a/builder/proxmox/iso/step_finalize_iso_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxmoxiso - -import ( - "context" - "fmt" - "testing" - - "github.com/Telmate/proxmox-api-go/proxmox" - "github.com/hashicorp/packer-plugin-sdk/multistep" - packersdk "github.com/hashicorp/packer-plugin-sdk/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", packersdk.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/iso/step_upload_iso.go b/builder/proxmox/iso/step_upload_iso.go deleted file mode 100644 index cc86654c..00000000 --- a/builder/proxmox/iso/step_upload_iso.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxmoxiso - -import ( - "context" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/Telmate/proxmox-api-go/proxmox" - "github.com/hashicorp/packer-plugin-sdk/multistep" - packersdk "github.com/hashicorp/packer-plugin-sdk/packer" -) - -// 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").(packersdk.Ui) - client := state.Get("proxmoxClient").(uploader) - c := state.Get("iso-config").(*Config) - - if !c.shouldUploadISO { - state.Put("iso_file", c.ISOFile) - return multistep.ActionContinue - } - - p := state.Get(downloadPathKey).(string) - if p == "" { - err := fmt.Errorf("Path to downloaded ISO was empty") - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - // All failure cases in resolving the symlink are caught anyway in os.Open - isoPath, _ := filepath.EvalSymlinks(p) - r, err := os.Open(isoPath) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - filename := filepath.Base(c.ISOUrls[0]) - err = client.Upload(c.Node, c.ISOStoragePool, "iso", filename, r) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename) - state.Put("iso_file", isoStoragePath) - - return multistep.ActionContinue -} - -func (s *stepUploadISO) Cleanup(state multistep.StateBag) { -} diff --git a/builder/proxmox/iso/step_upload_iso_test.go b/builder/proxmox/iso/step_upload_iso_test.go deleted file mode 100644 index 9317b45d..00000000 --- a/builder/proxmox/iso/step_upload_iso_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxmoxiso - -import ( - "context" - "fmt" - "io" - "testing" - - "github.com/hashicorp/packer-plugin-sdk/multistep" - "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" - packersdk "github.com/hashicorp/packer-plugin-sdk/packer" -) - -type uploaderMock struct { - fail bool - wasCalled bool -} - -func (m *uploaderMock) Upload(node string, storage string, contentType string, filename string, file io.Reader) error { - m.wasCalled = true - if m.fail { - return fmt.Errorf("Testing induced failure") - } - return nil -} - -var _ uploader = &uploaderMock{} - -func TestUploadISO(t *testing.T) { - cs := []struct { - name string - builderConfig *Config - downloadPath string - failUpload bool - - expectError bool - expectUploadCalled bool - expectedISOPath string - expectedAction multistep.StepAction - }{ - { - name: "should not call upload unless configured to do so", - builderConfig: &Config{shouldUploadISO: false, ISOFile: "local:iso/some-file"}, - - expectUploadCalled: false, - expectedISOPath: "local:iso/some-file", - expectedAction: multistep.ActionContinue, - }, - { - name: "success should continue", - builderConfig: &Config{ - shouldUploadISO: true, - ISOStoragePool: "local", - ISOConfig: commonsteps.ISOConfig{ISOUrls: []string{"http://server.example/some-file.iso"}}, - }, - downloadPath: "testdata/test.iso", - - expectedISOPath: "local:iso/some-file.iso", - expectUploadCalled: true, - expectedAction: multistep.ActionContinue, - }, - { - name: "failing upload should halt", - builderConfig: &Config{ - shouldUploadISO: true, - ISOStoragePool: "local", - ISOConfig: commonsteps.ISOConfig{ISOUrls: []string{"http://server.example/some-file.iso"}}, - }, - downloadPath: "testdata/test.iso", - failUpload: true, - - expectError: true, - expectUploadCalled: true, - expectedAction: multistep.ActionHalt, - }, - { - name: "downloader: state misconfiguration should halt", - builderConfig: &Config{ - shouldUploadISO: true, - ISOStoragePool: "local", - ISOConfig: commonsteps.ISOConfig{ISOUrls: []string{"http://server.example/some-file.iso"}}, - }, - - expectError: true, - expectUploadCalled: false, - expectedAction: multistep.ActionHalt, - }, - { - name: "downloader: file unreadable should halt", - builderConfig: &Config{ - shouldUploadISO: true, - ISOStoragePool: "local", - ISOConfig: commonsteps.ISOConfig{ISOUrls: []string{"http://server.example/some-file.iso"}}, - }, - downloadPath: "testdata/non-existent.iso", - - expectError: true, - expectUploadCalled: false, - expectedAction: multistep.ActionHalt, - }, - } - - for _, c := range cs { - t.Run(c.name, func(t *testing.T) { - m := &uploaderMock{fail: c.failUpload} - - state := new(multistep.BasicStateBag) - state.Put("ui", packersdk.TestUi(t)) - state.Put("iso-config", c.builderConfig) - state.Put(downloadPathKey, c.downloadPath) - state.Put("proxmoxClient", m) - - step := stepUploadISO{} - action := step.Run(context.TODO(), state) - step.Cleanup(state) - - if action != c.expectedAction { - t.Errorf("Expected action to be %v, got %v", c.expectedAction, action) - } - if m.wasCalled != c.expectUploadCalled { - t.Errorf("Expected mock to be called: %v, got: %v", c.expectUploadCalled, m.wasCalled) - } - err, gotError := state.GetOk("error") - if gotError != c.expectError { - t.Errorf("Expected error state to be: %v, got: %v", c.expectError, gotError) - } - if err == nil { - if isoPath := state.Get("iso_file"); isoPath != c.expectedISOPath { - if _, ok := isoPath.(string); !ok { - isoPath = "" - } - t.Errorf("Expected state iso_path to be %q, got %q", c.expectedISOPath, isoPath) - } - } - }) - } -} diff --git a/docs-partials/builder/proxmox/common/Config-not-required.mdx b/docs-partials/builder/proxmox/common/Config-not-required.mdx index f68be19b..bf8dea12 100644 --- a/docs-partials/builder/proxmox/common/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/common/Config-not-required.mdx @@ -134,8 +134,8 @@ - `cloud_init_disk_type` (string) - The type of Cloud-Init disk. Can be `scsi`, `sata`, or `ide` Defaults to `ide`. -- `additional_iso_files` ([]additionalISOsConfig) - Additional ISO files attached to the virtual machine. - See [Additional ISO Files](#additional-iso-files). +- `additional_iso_files` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISOs](#isos). - `vm_interface` (string) - Name of the network interface that Packer gets the VMs IP from. Defaults to the first non loopback interface. diff --git a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx new file mode 100644 index 00000000..951d37a4 --- /dev/null +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -0,0 +1,31 @@ + + +- `device` (string) - DEPRECATED. Assign bus type with `type`. Optionally assign a bus index with `index`. + Bus type and bus index that the ISO will be mounted on. Can be `ideX`, + `sataX` or `scsiX`. + For `ide` the bus index ranges from 0 to 3, for `sata` from 0 to 5 and for + `scsi` from 0 to 30. + Defaulted to `ide3` in versions up to v1.8, now defaults to dynamic ide assignment (next available ide bus index after hard disks are allocated) + +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + +- `index` (string) - Optional: Used in combination with `type` to statically assign an ISO to a bus index. + +- `iso_file` (string) - Path to the ISO file to boot from, expressed as a + proxmox datastore path, for example + `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. + Either `iso_file` OR `iso_url` must be specifed. + +- `iso_storage_pool` (string) - Proxmox storage pool onto which to upload + the ISO file. + +- `iso_download_pve` (bool) - Download the ISO directly from the PVE node rather than through Packer. + + Defaults to `false` + +- `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. + +- `keep_cdrom_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + + diff --git a/docs-partials/builder/proxmox/common/ISOsConfig.mdx b/docs-partials/builder/proxmox/common/ISOsConfig.mdx new file mode 100644 index 00000000..2a369cd2 --- /dev/null +++ b/docs-partials/builder/proxmox/common/ISOsConfig.mdx @@ -0,0 +1,32 @@ + + +ISO files attached to the virtual machine. + +JSON Example: + +```json + + "additional_iso_files": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + additional_iso_files { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + +``` + + diff --git a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx deleted file mode 100644 index 5de9fdbb..00000000 --- a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx +++ /dev/null @@ -1,23 +0,0 @@ - - -- `device` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ideX`, - `sataX` or `scsiX`. - For `ide` the bus index ranges from 0 to 3, for `sata` from 0 to 5 and for - `scsi` from 0 to 30. - Defaults to `ide3` since `ide2` is generally the boot drive. - -- `iso_file` (string) - Path to the ISO file to boot from, expressed as a - proxmox datastore path, for example - `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. - Either `iso_file` OR `iso_url` must be specifed. - -- `iso_storage_pool` (string) - Proxmox storage pool onto which to upload - the ISO file. - -- `iso_download_pve` (bool) - Download the ISO directly from the PVE node rather than through Packer. - - Defaults to `false` - -- `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. - - diff --git a/docs-partials/builder/proxmox/common/additionalISOsConfig.mdx b/docs-partials/builder/proxmox/common/additionalISOsConfig.mdx deleted file mode 100644 index 54e04a9b..00000000 --- a/docs-partials/builder/proxmox/common/additionalISOsConfig.mdx +++ /dev/null @@ -1,20 +0,0 @@ - - -Additional ISO files attached to the virtual machine. - -Example: - -```json -[ - - { - "device": "scsi5", - "iso_file": "local:iso/virtio-win-0.1.185.iso", - "unmount": true, - "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" - } - -] -``` - - diff --git a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx index 55cc3c87..7e880550 100644 --- a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx @@ -25,6 +25,12 @@ multiple disks are used. Requires `virtio-scsi-single` controller and a `scsi` or `virtio` disk. Defaults to `false`. +- `asyncio` (string) - Configure Asynchronous I/O. Can be `native`, `threads`, or `io_uring`. + Defaults to io_uring. + +- `exclude_from_backup` (bool) - Exclude disk from Proxmox backup jobs + Defaults to false. + - `discard` (bool) - Relay TRIM commands to the underlying storage. Defaults to false. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_hard_disk_discard) diff --git a/docs-partials/builder/proxmox/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index c9515418..57cf1f70 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -1,18 +1,22 @@ -- `iso_file` (string) - Path to the ISO file to boot from, expressed as a +- `iso_file` (string) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Path to the ISO file to boot from, expressed as a proxmox datastore path, for example `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. -- `iso_storage_pool` (string) - Proxmox storage pool onto which to upload +- `iso_storage_pool` (string) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Proxmox storage pool onto which to upload the ISO file. -- `iso_download_pve` (bool) - Download the ISO directly from the PVE node rather than through Packer. +- `iso_download_pve` (bool) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + Download the ISO directly from the PVE node rather than through Packer. Defaults to `false` -- `unmount_iso` (bool) - If true, remove the mounted ISO from the template +- `unmount_iso` (bool) - DEPRECATED. Define Boot ISO config with the `boot_iso` block instead. + If true, remove the mounted ISO from the template after finishing. Defaults to `false`. diff --git a/docs-partials/builder/proxmox/iso/Config-required.mdx b/docs-partials/builder/proxmox/iso/Config-required.mdx new file mode 100644 index 00000000..a7e80c86 --- /dev/null +++ b/docs-partials/builder/proxmox/iso/Config-required.mdx @@ -0,0 +1,31 @@ + + +- `boot_iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. + + JSON Example: + + ```json + + "boot_iso": { + "type": "scsi", + "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + "unmount": true, + "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + HCL2 example: + + ```hcl + + boot_iso { + type = "scsi" + iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" + unmount = true + iso_checksum = "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + See [ISOs](#isos) for additional options. + + diff --git a/docs/builders/clone.mdx b/docs/builders/clone.mdx index 48c63f94..ae2348cd 100644 --- a/docs/builders/clone.mdx +++ b/docs/builders/clone.mdx @@ -75,9 +75,9 @@ to you to use it or delete it. @include 'builder/proxmox/clone/cloudInitIpconfig-not-required.mdx' -### Additional ISO Files +### ISO Files -@include 'builder/proxmox/common/additionalISOsConfig.mdx' +@include 'builder/proxmox/common/ISOsConfig.mdx' @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig.mdx' @@ -89,7 +89,7 @@ to you to use it or delete it. @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-not-required.mdx' -@include 'builder/proxmox/common/additionalISOsConfig-not-required.mdx' +@include 'builder/proxmox/common/ISOsConfig-not-required.mdx' @include 'packer-plugin-sdk/multistep/commonsteps/CDConfig.mdx' diff --git a/docs/builders/iso.mdx b/docs/builders/iso.mdx index 772cb094..99ca64b4 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -29,7 +29,7 @@ to you to use it or delete it. ### Required: -@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-required.mdx' +@include 'builder/proxmox/iso/Config-required.mdx' ### Optional: @@ -39,11 +39,10 @@ to you to use it or delete it. @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-not-required.mdx' -### Additional ISO Files +### ISOs -@include 'builder/proxmox/common/additionalISOsConfig.mdx' +@include 'builder/proxmox/common/ISOsConfig.mdx' -@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig.mdx' #### Required @@ -53,7 +52,7 @@ to you to use it or delete it. @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-not-required.mdx' -@include 'builder/proxmox/common/additionalISOsConfig-not-required.mdx' +@include 'builder/proxmox/common/ISOsConfig-not-required.mdx' @include 'packer-plugin-sdk/multistep/commonsteps/CDConfig.mdx' @@ -164,7 +163,9 @@ source "proxmox-iso" "fedora-kickstart" { } http_directory = "config" insecure_skip_tls_verify = true - iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + iso { + iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } network_adapters { bridge = "vmbr0" model = "virtio" @@ -177,7 +178,6 @@ source "proxmox-iso" "fedora-kickstart" { ssh_username = "root" template_description = "Fedora 29-1.2, generated on ${timestamp()}" template_name = "fedora-29" - unmount_iso = true username = "${var.username}" } @@ -220,7 +220,9 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "iso": { + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + }, "http_directory": "config", "boot_wait": "10s", "boot_command": [ @@ -229,7 +231,6 @@ build { "ssh_username": "root", "ssh_timeout": "15m", "ssh_password": "packer", - "unmount_iso": true, "template_name": "fedora-29", "template_description": "Fedora 29-1.2, generated on {{ isotime \"2006-01-02T15:04:05Z\" }}" }