From 13a0b510868b418608669b6a57017b9902154bd4 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Wed, 29 May 2024 11:31:02 +0000 Subject: [PATCH 01/13] common: add disk config asyncio setting --- .web-docs/components/builder/clone/README.md | 3 ++ .web-docs/components/builder/iso/README.md | 3 ++ builder/proxmox/common/config.go | 6 +++ builder/proxmox/common/config.hcl2spec.go | 2 + builder/proxmox/common/step_start_vm.go | 4 ++ builder/proxmox/common/step_start_vm_test.go | 37 +++++++++++++++++++ .../common/diskConfig-not-required.mdx | 3 ++ 7 files changed, 58 insertions(+) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index d3c14707..5c64d02b 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -444,6 +444,9 @@ 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. + - `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/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 2336d807..be098393 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -659,6 +659,9 @@ 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. + - `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/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 3890928f..f32c5ec0 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -339,6 +339,9 @@ 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"` // 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) @@ -664,6 +667,9 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } } } + if disk.AsyncIO != "" && !(disk.AsyncIO == "native" || disk.AsyncIO == "threads" || disk.AsyncIO == "io_uring") { + 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")) } diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index e933239f..ca0391f1 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -338,6 +338,7 @@ type FlatdiskConfig struct { 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"` Discard *bool `mapstructure:"discard" cty:"discard" hcl:"discard"` SSD *bool `mapstructure:"ssd" cty:"ssd" hcl:"ssd"` } @@ -361,6 +362,7 @@ func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec { "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}, "discard": &hcldec.AttrSpec{Name: "discard", Type: cty.Bool, Required: false}, "ssd": &hcldec.AttrSpec{Name: "ssd", Type: cty.Bool, Required: false}, } diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 7c94eb0d..b31ee4a6 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -311,6 +311,7 @@ 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, @@ -352,6 +353,7 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { 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, @@ -368,6 +370,7 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { 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, @@ -383,6 +386,7 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { 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, diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 7ca87200..eb76d44b 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -527,6 +527,7 @@ 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, @@ -560,6 +561,7 @@ 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, @@ -594,6 +596,41 @@ func TestGenerateProxmoxDisks(t *testing.T) { Disk: &proxmox.QemuVirtIODisk{ SizeInKibibytes: 10485760, Storage: "local-lvm", + AsyncIO: proxmox.QemuDiskAsyncIO(""), + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + }, + }, + }, + }, + }, + { + "asyncio is native", + []diskConfig{ + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + AsyncIO: "native", + IOThread: true, + Discard: false, + SSD: 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, diff --git a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx index 55cc3c87..bbd36df3 100644 --- a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx @@ -25,6 +25,9 @@ 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. + - `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) From 6fdbe8a06f55cac6740c377fdbf7438a3d560542 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Wed, 29 May 2024 13:18:43 +0000 Subject: [PATCH 02/13] common: change iso unmount device default behaviour This commit changes the default behaviour for proxmox-iso with unmount_iso=true and additional_iso_files blocks with unmount=true to also remove the cdrom device from provisioned VM templates. A new configuration option unmount_keep_device allows templates to retain an empty cdrom device. --- .web-docs/components/builder/clone/README.md | 3 ++ .web-docs/components/builder/iso/README.md | 6 ++++ builder/proxmox/common/config.go | 5 +++- builder/proxmox/common/config.hcl2spec.go | 28 ++++++++++--------- .../common/step_finalize_template_config.go | 13 ++++++--- builder/proxmox/iso/config.go | 7 +++-- builder/proxmox/iso/config.hcl2spec.go | 2 ++ builder/proxmox/iso/step_finalize_iso.go | 6 +++- builder/proxmox/iso/step_finalize_iso_test.go | 15 ++++++++++ .../additionalISOsConfig-not-required.mdx | 3 ++ .../proxmox/iso/Config-not-required.mdx | 3 ++ 11 files changed, 70 insertions(+), 21 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 5c64d02b..8748ccf5 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -676,6 +676,9 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. +- `unmount_keep_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 be098393..e811d2bb 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -232,6 +232,9 @@ in the image's Cloud-Init settings for provisioning. - `unmount_iso` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. +- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + @@ -430,6 +433,9 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. +- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index f32c5ec0..22f7e729 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -241,7 +241,10 @@ 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` + UnmountKeepDevice bool `mapstructure:"unmount_keep_device"` ShouldUploadISO bool `mapstructure-to-hcl2:",skip"` DownloadPathKey string `mapstructure-to-hcl2:",skip"` commonsteps.CDConfig `mapstructure:",squash"` diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index ca0391f1..5491ec45 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -284,19 +284,20 @@ func (*FlatNICConfig) HCL2Spec() map[string]hcldec.Spec { // FlatadditionalISOsConfig is an auto-generated flat version of additionalISOsConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatadditionalISOsConfig struct { - ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` - RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` - ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` - TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` - TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` - Device *string `mapstructure:"device" cty:"device" hcl:"device"` - ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` - ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` - ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` - Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"` - 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"` + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + Device *string `mapstructure:"device" cty:"device" hcl:"device"` + ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` + ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` + ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` + Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"` + UnmountKeepDevice *bool `mapstructure:"unmount_keep_device" cty:"unmount_keep_device" hcl:"unmount_keep_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. @@ -321,6 +322,7 @@ func (*FlatadditionalISOsConfig) 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": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, + "unmount_keep_device": &hcldec.AttrSpec{Name: "unmount_keep_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}, diff --git a/builder/proxmox/common/step_finalize_template_config.go b/builder/proxmox/common/step_finalize_template_config.go index 9fdcdf8f..4aa3b64b 100644 --- a/builder/proxmox/common/step_finalize_template_config.go +++ b/builder/proxmox/common/step_finalize_template_config.go @@ -105,6 +105,7 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St } } + deleteItems := []string{} if len(c.AdditionalISOFiles) > 0 { for idx := range c.AdditionalISOFiles { cdrom := c.AdditionalISOFiles[idx].Device @@ -115,7 +116,11 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St ui.Error(err.Error()) return multistep.ActionHalt } - changes[cdrom] = "none,media=cdrom" + if c.AdditionalISOFiles[idx].UnmountKeepDevice { + changes[cdrom] = "none,media=cdrom" + } else { + deleteItems = append(deleteItems, cdrom) + } } else { changes[cdrom] = c.AdditionalISOFiles[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/iso/config.go b/builder/proxmox/iso/config.go index b9772392..13d46476 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -32,8 +32,11 @@ type Config struct { ISODownloadPVE bool `mapstructure:"iso_download_pve"` // 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"` + // Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + // Has no effect if unmount is `false` + UnmountKeepDevice bool `mapstructure:"unmount_keep_device"` + shouldUploadISO bool } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index cea97b57..c02752ee 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -129,6 +129,7 @@ type FlatConfig struct { 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"` + UnmountKeepDevice *bool `mapstructure:"unmount_keep_device" cty:"unmount_keep_device" hcl:"unmount_keep_device"` } // FlatMapstructure returns a new FlatConfig. @@ -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}, + "unmount_keep_device": &hcldec.AttrSpec{Name: "unmount_keep_device", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/proxmox/iso/step_finalize_iso.go b/builder/proxmox/iso/step_finalize_iso.go index 21f16ee9..5fdd65e2 100644 --- a/builder/proxmox/iso/step_finalize_iso.go +++ b/builder/proxmox/iso/step_finalize_iso.go @@ -47,7 +47,11 @@ func (s *stepFinalizeISOTemplate) Run(ctx context.Context, state multistep.State ui.Error(err.Error()) return multistep.ActionHalt } - changes["ide2"] = "none,media=cdrom" + if c.UnmountKeepDevice { + changes["ide2"] = "none,media=cdrom" + } else { + changes["delete"] = "ide2" + } } if len(changes) > 0 { diff --git a/builder/proxmox/iso/step_finalize_iso_test.go b/builder/proxmox/iso/step_finalize_iso_test.go index 82d37493..b5aff576 100644 --- a/builder/proxmox/iso/step_finalize_iso_test.go +++ b/builder/proxmox/iso/step_finalize_iso_test.go @@ -59,6 +59,21 @@ func TestISOTemplateFinalize(t *testing.T) { "ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom", }, expectCallSetConfig: true, + expectedVMConfig: map[string]interface{}{ + "ide2": nil, + }, + expectedAction: multistep.ActionContinue, + }, + { + name: "should keep iso device and unmount ISO when configured", + builderConfig: &Config{ + UnmountISO: true, + UnmountKeepDevice: 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", }, diff --git a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx index 5de9fdbb..6abb11a8 100644 --- a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx @@ -20,4 +20,7 @@ - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. +- `unmount_keep_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/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index c9515418..43809c9d 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -15,4 +15,7 @@ - `unmount_iso` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. +- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. + Has no effect if unmount is `false` + From a78726c4df78eeb4d07f49acb82c54311fff6cd4 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Fri, 31 May 2024 13:49:54 +0000 Subject: [PATCH 03/13] iso: add iso_device config option Enabled device type and index assignment to the default ISO required by proxmox-iso. In testing it was observed that when statically assigning a device type and slot to an Additional ISO block it would overwrite iterative Disk index assignments. This would have been existing behaviour in >1.8. Logic is added in this commit to reserve the default ISO and additional ISO file block's configured bus and index then assign disks around these. Validation added to ensure default ISO and an Additional ISO cannot be assigned the same bus index. In testing it was observed there was no validation to prevent overassignment of devices to a bus, eg. IDE has a max capacity of 4 devices, SATA 6 devices. This would have been existing behaviour in >1.8. Logic added in this commit to proxmox-iso to validate total assignments to each bus don't exceed their limits. --- .web-docs/components/builder/iso/README.md | 6 + builder/proxmox/common/config.go | 34 +-- builder/proxmox/common/step_start_vm.go | 212 +++++++++++++----- builder/proxmox/common/step_start_vm_test.go | 148 +++++++++++- builder/proxmox/iso/builder.go | 58 ++++- builder/proxmox/iso/config.go | 94 ++++++++ builder/proxmox/iso/config.hcl2spec.go | 2 + .../proxmox/iso/Config-not-required.mdx | 6 + 8 files changed, 470 insertions(+), 90 deletions(-) diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index e811d2bb..d18262be 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -222,6 +222,12 @@ in the image's Cloud-Init settings for provisioning. `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. +- `iso_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 `ide2` + - `iso_storage_pool` (string) - Proxmox storage pool onto which to upload the ISO file. diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 22f7e729..e77aa7d6 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -201,6 +201,11 @@ type Config struct { // Note: this option is for experts only. AdditionalArgs string `mapstructure:"qemu_additional_args"` + // internal use when running a proxmox-iso build. + // proxmox-iso Prepare will set to the device type and index value + // for processing in step_start_vm.go func generateProxmoxDisks + ISOBuilderCDROMDevice string `mapstructure-to-hcl2:",skip"` + Ctx interpolate.Context `mapstructure-to-hcl2:",skip"` } @@ -642,10 +647,6 @@ 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 for idx, disk := range c.Disks { if disk.Type == "" { log.Printf("Disk %d type not set, using default 'scsi'", idx) @@ -682,28 +683,6 @@ 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))) @@ -826,6 +805,9 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30")) } } + if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "virtio") { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("VirtIO is not a supported device type for ISOs")) + } 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")) diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index b31ee4a6..ca8e13ff 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" + "regexp" "strconv" "strings" @@ -130,7 +131,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: generateProxmoxDisks(c.Disks, c.AdditionalISOFiles, c.ISOBuilderCDROMDevice), QemuPCIDevices: generateProxmoxPCIDeviceMap(c.PCIDevices), QemuSerials: generateProxmoxSerials(c.Serials), Scsihw: c.SCSIController, @@ -207,22 +208,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 +265,73 @@ func generateProxmoxNetworkAdapters(nics []NICConfig) proxmox.QemuDevices { return devs } -func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { +func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISOsConfig, bootiso string) *proxmox.QemuStorages { ideDisks := proxmox.QemuIdeDisks{} sataDisks := proxmox.QemuSataDisks{} scsiDisks := proxmox.QemuScsiDisks{} virtIODisks := proxmox.QemuVirtIODisks{} + // additionalISOsConfig accepts a static device type and index value in Device. + // Disks accept a device type but no index value. + // + // If this is a proxmox-iso build, the boot iso device is mapped after this function (builder/proxmox/iso/builder.go func Create) + // Map Additional ISO files first to ensure they get their assigned device index then proceed with disks in remaining available fields. + if len(additionalISOFiles) > 0 { + for _, iso := range additionalISOFiles { + // IsoFile struct parses the ISO File and Storage Pool as separate fields. + isoFile := strings.Split(iso.ISOFile, ":iso/") + + // define QemuCdRom containing isoFile properties + bootIso := &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: isoFile[1], + Storage: isoFile[0], + }, + } + + // extract device type from ISODevice config value eg. ide from ide2 + // validation of the iso.Device value occurs in builder/proxmox/common/config.go func Prepare + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(iso.Device) + // extract device index from ISODevice config value eg. 2 from ide2 + rb := regexp.MustCompile(`\d+`) + index, _ := strconv.Atoi(rb.FindString(iso.Device)) + + log.Printf("Mapping Additional ISO to %s%d", device, index) + switch device { + case "ide": + dev := proxmox.QemuIdeStorage{ + CdRom: bootIso, + } + reflect. + ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) + case "sata": + dev := proxmox.QemuSataStorage{ + CdRom: bootIso, + } + reflect. + ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) + case "scsi": + dev := proxmox.QemuScsiStorage{ + CdRom: bootIso, + } + reflect.ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) + } + } + } + ideCount := 0 sataCount := 0 scsiCount := 0 virtIOCount := 0 + // Map Disks for idx := range disks { tmpSize, _ := strconv.ParseInt(disks[idx].Size[:len(disks[idx].Size)-1], 10, 0) size := proxmox.QemuDiskSize(0) @@ -318,36 +359,51 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { EmulateSSD: disks[idx].SSD, }, } - // 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) + // 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. + if 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)). + // Return if the field has no device already attached to it + // and not proxmox-iso's configured boot iso device index + IsNil() && bootiso != fmt.Sprintf("ide%d", ideCount) { + reflect. + ValueOf(&ideDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", ideCount)). + // Assign dev to the Disk_%d field + 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{ @@ -361,10 +417,22 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { IOThread: disks[idx].IOThread, }, } - 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 reflect. + ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + IsNil() && bootiso != fmt.Sprintf("scsi%d", scsiCount) { + 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{ @@ -377,10 +445,22 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { EmulateSSD: disks[idx].SSD, }, } - reflect.ValueOf(&sataDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", sataCount)). - Set(reflect.ValueOf(&dev)) - sataCount++ + for { + log.Printf("Mapping Disk to sata%d", sataCount) + if reflect. + ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + IsNil() && bootiso != fmt.Sprintf("sata%d", sataCount) { + 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{ @@ -393,10 +473,22 @@ func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { IOThread: disks[idx].IOThread, }, } - 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 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{ diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index eb76d44b..57bed9f0 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -503,6 +503,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { tests := []struct { name string disks []diskConfig + isos []additionalISOsConfig + bootiso string expectOutput *proxmox.QemuStorages }{ { @@ -519,6 +521,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, + []additionalISOsConfig{}, + "", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -553,6 +557,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, + []additionalISOsConfig{}, + "", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -587,6 +593,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, + []additionalISOsConfig{}, + "", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -621,6 +629,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, + []additionalISOsConfig{}, + "", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -708,6 +718,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { IOThread: true, }, }, + []additionalISOsConfig{}, + "", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -795,11 +807,145 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, }, + { + "bunch of disks, Additional ISOs and boot iso", + []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, + }, + }, + []additionalISOsConfig{ + { + Device: "sata0", + ISOFile: "local:iso/test.iso", + }, + }, + "ide2", + &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, + }, + }, + Disk_1: &proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: 12582912, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + }, + }, + // while ide2 is specified for bootiso, the actual mount of the ISO for proxmox-iso occurs in the its builder func. + // logic in generateProxmoxDisks should only make sure no disks are allocated to ide2 to prevent conflict when allocating disks + Disk_3: &proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + }, + }, + }, + Sata: &proxmox.QemuSataDisks{ + Disk_0: &proxmox.QemuSataStorage{ + CdRom: &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: "test.iso", + Storage: "local", + }, + }, + }, + Disk_1: &proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: 11534336, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + }, + }, + Disk_2: &proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: 13631488, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + }, + }, + }, + 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, + }, + }, + }, + VirtIO: &proxmox.QemuVirtIODisks{}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - devs := generateProxmoxDisks(tt.disks) + devs := generateProxmoxDisks(tt.disks, tt.isos, tt.bootiso) assert.Equal(t, devs, tt.expectOutput) }) } diff --git a/builder/proxmox/iso/builder.go b/builder/proxmox/iso/builder.go index cafdb7e6..d00e5330 100644 --- a/builder/proxmox/iso/builder.go +++ b/builder/proxmox/iso/builder.go @@ -5,6 +5,11 @@ package proxmoxiso import ( "context" + "fmt" + "log" + "reflect" + "regexp" + "strconv" "strings" proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" @@ -71,10 +76,57 @@ 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 { + // get iso config from state + c := state.Get("iso-config").(*Config) + // IsoFile struct parses the ISO File and Storage Pool as separate fields. isoFile := strings.Split(state.Get("iso_file").(string), ":iso/") - config.Iso = &proxmoxapi.IsoFile{ - File: isoFile[1], - Storage: isoFile[0], + + // define QemuCdRom struct containing isoFile properties + bootIso := &proxmoxapi.QemuCdRom{ + Iso: &proxmoxapi.IsoFile{ + File: isoFile[1], + Storage: isoFile[0], + }, + } + + // extract device type from ISODevice config value eg. ide from ide2 + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(c.ISODevice) + // extract device index from ISODevice config value eg. 2 from ide2 + rb := regexp.MustCompile(`\d+`) + index, _ := strconv.Atoi(rb.FindString(c.ISODevice)) + + // Assign bootIso QemuCdRom struct to configured ISODevice device type and index + // + // The boot ISO is mapped to a device type and index after Disks and Additional ISO Files + // (builder/proxmox/common/step_start_vm.go func generateProxmoxDisks) + // generateProxmoxDisks contains logic to avoid mapping conflicts + log.Printf("Mapping Boot ISO to %s%d", device, index) + switch device { + case "ide": + dev := proxmoxapi.QemuIdeStorage{ + CdRom: bootIso, + } + reflect. + ValueOf(config.Disks.Ide).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) + case "scsi": + dev := proxmoxapi.QemuScsiStorage{ + CdRom: bootIso, + } + reflect. + ValueOf(config.Disks.Scsi).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) + case "sata": + dev := proxmoxapi.QemuSataStorage{ + CdRom: bootIso, + } + reflect. + ValueOf(config.Disks.Sata).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", index)). + Set(reflect.ValueOf(&dev)) } client := state.Get("proxmoxClient").(*proxmoxapi.Client) diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go index 13d46476..068f5524 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -8,6 +8,10 @@ package proxmoxiso import ( "errors" + "fmt" + "log" + "regexp" + "strconv" proxmoxcommon "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" @@ -23,6 +27,12 @@ type Config struct { // `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"` + // 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 `ide2` + ISODevice string `mapstructure:"iso_device"` // Proxmox storage pool onto which to upload // the ISO file. ISOStoragePool string `mapstructure:"iso_storage_pool"` @@ -68,6 +78,90 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified")) } + // each device type has a maximum number of devices that can be attached. + // count iso, disks, additional isos configured for each device type, error if too many. + ideCount := 0 + sataCount := 0 + scsiCount := 0 + virtIOCount := 0 + + if c.ISODevice == "" { + // set default ISO boot device ide2 if no value specified + log.Printf("iso_device not set, using default 'ide2'") + c.ISODevice = "ide2" + ideCount++ + // Pass c.ISODevice value over to common config to ensure device index not used by disks + c.ISOBuilderCDROMDevice = "ide2" + } else { + // Pass c.ISODevice value over to common config to ensure device index not used by disks + c.ISOBuilderCDROMDevice = c.ISODevice + // get device from ISODevice config + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(c.ISODevice) + // get index from ISODevice config + rb := regexp.MustCompile(`\d+`) + _, err := strconv.Atoi(rb.FindString(c.ISODevice)) + if err != nil { + errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device value doesn't contain a valid index number. Expected format is , eg. scsi0. received value: "+c.ISODevice)) + } + // count iso + switch device { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + } + for idx, iso := range c.AdditionalISOFiles { + if iso.Device == c.ISODevice { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("conflicting device assignment between iso_device (%s) and additional_iso_files block %d device", c.ISODevice, idx+1)) + } + // count additional isos + // get device from iso.Device + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(iso.Device) + switch device { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + } + } + if !(device == "ide" || device == "sata" || device == "scsi") { + errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device must be of type ide, sata or scsi. VirtIO not supported for ISO devices")) + } + } + + // count disks + for _, disks := range c.Disks { + switch disks.Type { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + case "virtio": + virtIOCount++ + } + } + // validate device type allocations + if ideCount > 4 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 4 IDE disks and ISOs supported")) + } + if sataCount > 6 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 6 SATA disks and ISOs supported")) + } + if scsiCount > 31 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 31 SCSI disks and ISOs supported")) + } + if virtIOCount > 16 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 16 VirtIO disks supported")) + } + if errs != nil && len(errs.Errors) > 0 { return nil, warnings, errs } diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index c02752ee..0e2ac84d 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -126,6 +126,7 @@ type FlatConfig struct { 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"` + ISODevice *string `mapstructure:"iso_device" cty:"iso_device" hcl:"iso_device"` 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"` @@ -259,6 +260,7 @@ func (*FlatConfig) 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}, "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, + "iso_device": &hcldec.AttrSpec{Name: "iso_device", 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_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false}, diff --git a/docs-partials/builder/proxmox/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index 43809c9d..f7b1509c 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -5,6 +5,12 @@ `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. +- `iso_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 `ide2` + - `iso_storage_pool` (string) - Proxmox storage pool onto which to upload the ISO file. From 064a8b5dad7b8b26c36044ef1b9dadb0320826f6 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Sat, 1 Jun 2024 11:44:00 +0000 Subject: [PATCH 04/13] fix: replace ide2 static references in finalize_iso --- builder/proxmox/iso/step_finalize_iso.go | 8 ++++---- builder/proxmox/iso/step_finalize_iso_test.go | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/builder/proxmox/iso/step_finalize_iso.go b/builder/proxmox/iso/step_finalize_iso.go index 5fdd65e2..0f9ceff5 100644 --- a/builder/proxmox/iso/step_finalize_iso.go +++ b/builder/proxmox/iso/step_finalize_iso.go @@ -41,16 +41,16 @@ func (s *stepFinalizeISOTemplate) Run(ctx context.Context, state multistep.State 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") + if vmParams[c.ISODevice] == nil || !strings.Contains(vmParams[c.ISODevice].(string), "media=cdrom") { + err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present, or not a cdrom media", c.ISODevice) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } if c.UnmountKeepDevice { - changes["ide2"] = "none,media=cdrom" + changes[c.ISODevice] = "none,media=cdrom" } else { - changes["delete"] = "ide2" + changes["delete"] = c.ISODevice } } diff --git a/builder/proxmox/iso/step_finalize_iso_test.go b/builder/proxmox/iso/step_finalize_iso_test.go index b5aff576..b1ac374c 100644 --- a/builder/proxmox/iso/step_finalize_iso_test.go +++ b/builder/proxmox/iso/step_finalize_iso_test.go @@ -54,6 +54,7 @@ func TestISOTemplateFinalize(t *testing.T) { name: "should unmount when configured", builderConfig: &Config{ UnmountISO: true, + ISODevice: "ide2", }, initialVMConfig: map[string]interface{}{ "ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom", @@ -68,6 +69,7 @@ func TestISOTemplateFinalize(t *testing.T) { name: "should keep iso device and unmount ISO when configured", builderConfig: &Config{ UnmountISO: true, + ISODevice: "ide2", UnmountKeepDevice: true, }, initialVMConfig: map[string]interface{}{ From 7b4fd6431a303617949bb604e0df99d67222f434 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Tue, 4 Jun 2024 14:09:24 +0000 Subject: [PATCH 05/13] fix: code quality updates - Rename UnmountKeepDevice to KeepCDRomDevice - Added for loop limit for proxmoxGenerateDisks - Added disk validation for clone builder - Code quality improvements to iso builder storage validation --- .web-docs/components/builder/clone/README.md | 2 +- .web-docs/components/builder/iso/README.md | 4 +- builder/proxmox/clone/config.go | 48 ++++++++++++ builder/proxmox/common/config.go | 9 ++- builder/proxmox/common/config.hcl2spec.go | 30 ++++---- .../common/step_finalize_template_config.go | 2 +- builder/proxmox/common/step_start_vm.go | 17 +++++ builder/proxmox/iso/config.go | 75 ++++++++++--------- builder/proxmox/iso/config.hcl2spec.go | 4 +- builder/proxmox/iso/step_finalize_iso.go | 2 +- builder/proxmox/iso/step_finalize_iso_test.go | 6 +- .../additionalISOsConfig-not-required.mdx | 2 +- .../proxmox/iso/Config-not-required.mdx | 2 +- 13 files changed, 138 insertions(+), 65 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 8748ccf5..6548f8a8 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -676,7 +676,7 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. -- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. 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 d18262be..5890d143 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -238,7 +238,7 @@ in the image's Cloud-Init settings for provisioning. - `unmount_iso` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. -- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. 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` @@ -439,7 +439,7 @@ In HCL2: - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. -- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. 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/builder/proxmox/clone/config.go b/builder/proxmox/clone/config.go index aa6de538..48f2bb94 100644 --- a/builder/proxmox/clone/config.go +++ b/builder/proxmox/clone/config.go @@ -11,6 +11,7 @@ import ( "fmt" "net" "net/netip" + "regexp" "strings" proxmoxcommon "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" @@ -129,6 +130,53 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%d ipconfig blocks given, but only %d network interfaces defined", len(c.Ipconfigs), len(c.NICs))) } + // each device type has a maximum number of devices that can be attached. + // count disks, additional isos configured for each device type, error if too many. + ideCount := 0 + sataCount := 0 + scsiCount := 0 + virtIOCount := 0 + // count disks + for _, disks := range c.Disks { + switch disks.Type { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + case "virtio": + virtIOCount++ + } + } + // count additional_iso_files devices + for _, iso := range c.AdditionalISOFiles { + // get device type from iso.Device + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(iso.Device) + switch device { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + } + } + // validate device type allocations + if ideCount > 4 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 4 IDE disks and ISOs supported")) + } + if sataCount > 6 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 6 SATA disks and ISOs supported")) + } + if scsiCount > 31 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 31 SCSI disks and ISOs supported")) + } + if virtIOCount > 16 { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 16 VirtIO disks supported")) + } + if errs != nil && len(errs.Errors) > 0 { return nil, warnings, errs } diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index e77aa7d6..aa8d6be3 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -249,7 +249,7 @@ type additionalISOsConfig struct { Unmount bool `mapstructure:"unmount"` // Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. // Has no effect if unmount is `false` - UnmountKeepDevice bool `mapstructure:"unmount_keep_device"` + KeepCDRomDevice bool `mapstructure:"keep_cdrom_device"` ShouldUploadISO bool `mapstructure-to-hcl2:",skip"` DownloadPathKey string `mapstructure-to-hcl2:",skip"` commonsteps.CDConfig `mapstructure:",squash"` @@ -671,7 +671,12 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } } } - if disk.AsyncIO != "" && !(disk.AsyncIO == "native" || disk.AsyncIO == "threads" || disk.AsyncIO == "io_uring") { + 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" { diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index 5491ec45..568cdd5b 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -284,20 +284,20 @@ func (*FlatNICConfig) HCL2Spec() map[string]hcldec.Spec { // FlatadditionalISOsConfig is an auto-generated flat version of additionalISOsConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatadditionalISOsConfig struct { - ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` - RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` - ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` - TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` - TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` - Device *string `mapstructure:"device" cty:"device" hcl:"device"` - ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` - ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` - ISODownloadPVE *bool `mapstructure:"iso_download_pve" cty:"iso_download_pve" hcl:"iso_download_pve"` - Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"` - UnmountKeepDevice *bool `mapstructure:"unmount_keep_device" cty:"unmount_keep_device" hcl:"unmount_keep_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"` + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + Device *string `mapstructure:"device" cty:"device" hcl:"device"` + ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` + ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` + 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. @@ -322,7 +322,7 @@ func (*FlatadditionalISOsConfig) 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": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, - "unmount_keep_device": &hcldec.AttrSpec{Name: "unmount_keep_device", 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}, diff --git a/builder/proxmox/common/step_finalize_template_config.go b/builder/proxmox/common/step_finalize_template_config.go index 4aa3b64b..da17edc4 100644 --- a/builder/proxmox/common/step_finalize_template_config.go +++ b/builder/proxmox/common/step_finalize_template_config.go @@ -116,7 +116,7 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St ui.Error(err.Error()) return multistep.ActionHalt } - if c.AdditionalISOFiles[idx].UnmountKeepDevice { + if c.AdditionalISOFiles[idx].KeepCDRomDevice { changes[cdrom] = "none,media=cdrom" } else { deleteItems = append(deleteItems, cdrom) diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index ca8e13ff..53fb830a 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -361,6 +361,11 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } for { log.Printf("Mapping Disk to ide%d", ideCount) + // to avoid a panic if IDE has too many devices attached, exit the loop when all indexes are occupied + if ideCount > 3 { + log.Print("No further IDE device indexes available (Max 4 devices).") + break + } // 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. @@ -419,6 +424,10 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } for { log.Printf("Mapping Disk to scsi%d", scsiCount) + if scsiCount > 30 { + log.Print("No further SCSI device indexes available (Max 31 devices).") + break + } if reflect. ValueOf(&scsiDisks).Elem(). FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). @@ -446,6 +455,10 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO }, } for { + if sataCount > 5 { + log.Print("No further SATA device indexes available (Max 6 devices).") + break + } log.Printf("Mapping Disk to sata%d", sataCount) if reflect. ValueOf(&sataDisks).Elem(). @@ -475,6 +488,10 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } for { log.Printf("Mapping Disk to virtio%d", virtIOCount) + if virtIOCount > 15 { + log.Print("No further VirtIO device indexes available (Max 16 devices).") + break + } if reflect. ValueOf(&virtIODisks).Elem(). FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go index 068f5524..4f6aec18 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -45,8 +45,8 @@ type Config struct { UnmountISO bool `mapstructure:"unmount_iso"` // Keep CDRom device attached to template if unmounting ISO. Defaults to `false`. // Has no effect if unmount is `false` - UnmountKeepDevice bool `mapstructure:"unmount_keep_device"` - shouldUploadISO bool + KeepCDRomDevice bool `mapstructure:"keep_cdrom_device"` + shouldUploadISO bool } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { @@ -89,22 +89,45 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { // set default ISO boot device ide2 if no value specified log.Printf("iso_device not set, using default 'ide2'") c.ISODevice = "ide2" - ideCount++ - // Pass c.ISODevice value over to common config to ensure device index not used by disks + // Pass c.ISODevice value over to common config to reserve device index c.ISOBuilderCDROMDevice = "ide2" - } else { - // Pass c.ISODevice value over to common config to ensure device index not used by disks - c.ISOBuilderCDROMDevice = c.ISODevice - // get device from ISODevice config - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(c.ISODevice) - // get index from ISODevice config - rb := regexp.MustCompile(`\d+`) - _, err := strconv.Atoi(rb.FindString(c.ISODevice)) - if err != nil { - errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device value doesn't contain a valid index number. Expected format is , eg. scsi0. received value: "+c.ISODevice)) + ideCount++ + } + + // get device from ISODevice config + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(c.ISODevice) + // get index from ISODevice config + rb := regexp.MustCompile(`\d+`) + _, err := strconv.Atoi(rb.FindString(c.ISODevice)) + if err != nil { + errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device value doesn't contain a valid index number. Expected format is , eg. scsi0. received value: "+c.ISODevice)) + } + // count iso + switch device { + case "ide": + ideCount++ + case "sata": + sataCount++ + case "scsi": + scsiCount++ + default: + errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device must be of type ide, sata or scsi. VirtIO not supported for ISO devices")) + } + // Pass c.ISODevice value over to common config to reserve device index + c.ISOBuilderCDROMDevice = c.ISODevice + + // count additional_iso_files devices + for idx, iso := range c.AdditionalISOFiles { + // Prevent a device assignment conflict by ensuring ISOs defined in c.AdditionalISOFiles + // aren't assigned to the same device and index as the boot ISO device. + if iso.Device == c.ISODevice { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("conflicting device assignment between iso_device (%s) and additional_iso_files block %d device", c.ISODevice, idx+1)) } - // count iso + // count additional isos + // get device from iso.Device + rd := regexp.MustCompile(`\D+`) + device := rd.FindString(iso.Device) switch device { case "ide": ideCount++ @@ -113,26 +136,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { case "scsi": scsiCount++ } - for idx, iso := range c.AdditionalISOFiles { - if iso.Device == c.ISODevice { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("conflicting device assignment between iso_device (%s) and additional_iso_files block %d device", c.ISODevice, idx+1)) - } - // count additional isos - // get device from iso.Device - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(iso.Device) - switch device { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - } - } - if !(device == "ide" || device == "sata" || device == "scsi") { - errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device must be of type ide, sata or scsi. VirtIO not supported for ISO devices")) - } } // count disks diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index 0e2ac84d..c31b74ae 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -130,7 +130,7 @@ type FlatConfig struct { 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"` - UnmountKeepDevice *bool `mapstructure:"unmount_keep_device" cty:"unmount_keep_device" hcl:"unmount_keep_device"` + KeepCDRomDevice *bool `mapstructure:"keep_cdrom_device" cty:"keep_cdrom_device" hcl:"keep_cdrom_device"` } // FlatMapstructure returns a new FlatConfig. @@ -264,7 +264,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}, - "unmount_keep_device": &hcldec.AttrSpec{Name: "unmount_keep_device", Type: cty.Bool, Required: false}, + "keep_cdrom_device": &hcldec.AttrSpec{Name: "keep_cdrom_device", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/proxmox/iso/step_finalize_iso.go b/builder/proxmox/iso/step_finalize_iso.go index 0f9ceff5..73caa78d 100644 --- a/builder/proxmox/iso/step_finalize_iso.go +++ b/builder/proxmox/iso/step_finalize_iso.go @@ -47,7 +47,7 @@ func (s *stepFinalizeISOTemplate) Run(ctx context.Context, state multistep.State ui.Error(err.Error()) return multistep.ActionHalt } - if c.UnmountKeepDevice { + if c.KeepCDRomDevice { changes[c.ISODevice] = "none,media=cdrom" } else { changes["delete"] = c.ISODevice diff --git a/builder/proxmox/iso/step_finalize_iso_test.go b/builder/proxmox/iso/step_finalize_iso_test.go index b1ac374c..f0f9edb2 100644 --- a/builder/proxmox/iso/step_finalize_iso_test.go +++ b/builder/proxmox/iso/step_finalize_iso_test.go @@ -68,9 +68,9 @@ func TestISOTemplateFinalize(t *testing.T) { { name: "should keep iso device and unmount ISO when configured", builderConfig: &Config{ - UnmountISO: true, - ISODevice: "ide2", - UnmountKeepDevice: true, + UnmountISO: true, + ISODevice: "ide2", + KeepCDRomDevice: true, }, initialVMConfig: map[string]interface{}{ "ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom", diff --git a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx index 6abb11a8..a846e63b 100644 --- a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx @@ -20,7 +20,7 @@ - `unmount` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. -- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. 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/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index f7b1509c..821c0f34 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -21,7 +21,7 @@ - `unmount_iso` (bool) - If true, remove the mounted ISO from the template after finishing. Defaults to `false`. -- `unmount_keep_device` (bool) - Keep CDRom device attached to template if unmounting ISO. 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` From 99efad8df01c0900dd09850fd43606833987f9af Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Wed, 12 Jun 2024 13:14:47 +0000 Subject: [PATCH 06/13] consolidate iso device configuration - this reroll merges iso builder's iso device and common's additional_iso_files into a single common iso struct. - disk and iso validation is now handled centrally in common - generateProxmoxDisks handles overassignment of device types with returned ui and state errors - new prestep added to clone builder to map source vm storage before packer disk and iso enumeration. Enables appending of new disks to a cloned vm. --- .web-docs/components/builder/clone/README.md | 54 +-- .web-docs/components/builder/iso/README.md | 113 ++++--- builder/proxmox/clone/builder.go | 1 + builder/proxmox/clone/config.go | 48 --- builder/proxmox/clone/config.hcl2spec.go | 232 ++++++------- .../proxmox/clone/step_map_source_disks.go | 99 ++++++ builder/proxmox/common/builder.go | 28 +- builder/proxmox/common/config.go | 203 ++++++----- builder/proxmox/common/config.hcl2spec.go | 318 +++++++++--------- builder/proxmox/common/config_test.go | 64 ++-- .../common/step_download_iso_on_pve.go | 2 +- .../common/step_finalize_template_config.go | 12 +- builder/proxmox/common/step_start_vm.go | 245 ++++++++------ builder/proxmox/common/step_start_vm_test.go | 49 ++- ...d_additional_iso.go => step_upload_iso.go} | 10 +- ...al_iso_test.go => step_upload_iso_test.go} | 36 +- builder/proxmox/iso/builder.go | 86 +---- builder/proxmox/iso/config.go | 143 +------- builder/proxmox/iso/config.hcl2spec.go | 242 ++++++------- builder/proxmox/iso/config_test.go | 20 +- .../proxmox/iso/step_download_iso_on_pve.go | 42 --- builder/proxmox/iso/step_finalize_iso.go | 71 ---- builder/proxmox/iso/step_finalize_iso_test.go | 151 --------- builder/proxmox/iso/step_upload_iso.go | 69 ---- builder/proxmox/iso/step_upload_iso_test.go | 140 -------- .../proxmox/common/Config-not-required.mdx | 4 +- ...quired.mdx => ISOsConfig-not-required.mdx} | 14 +- .../builder/proxmox/common/ISOsConfig.mdx | 32 ++ .../proxmox/common/additionalISOsConfig.mdx | 20 -- .../proxmox/iso/Config-not-required.mdx | 23 -- docs/builders/clone.mdx | 6 +- docs/builders/iso.mdx | 10 +- 32 files changed, 983 insertions(+), 1604 deletions(-) create mode 100644 builder/proxmox/clone/step_map_source_disks.go rename builder/proxmox/common/{step_upload_additional_iso.go => step_upload_iso.go} (90%) rename builder/proxmox/common/{step_upload_additional_iso_test.go => step_upload_iso_test.go} (90%) delete mode 100644 builder/proxmox/iso/step_download_iso_on_pve.go delete mode 100644 builder/proxmox/iso/step_finalize_iso.go delete mode 100644 builder/proxmox/iso/step_finalize_iso_test.go delete mode 100644 builder/proxmox/iso/step_upload_iso.go delete mode 100644 builder/proxmox/iso/step_upload_iso_test.go rename docs-partials/builder/proxmox/common/{additionalISOsConfig-not-required.mdx => ISOsConfig-not-required.mdx} (50%) create mode 100644 docs-partials/builder/proxmox/common/ISOsConfig.mdx delete mode 100644 docs-partials/builder/proxmox/common/additionalISOsConfig.mdx diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 6548f8a8..cab8d21a 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). +- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISO Files](#iso-files). - `vm_interface` (string) - Name of the network interface that Packer gets the VMs IP from. Defaults to the first non loopback interface. @@ -498,28 +498,40 @@ Usage example (JSON): -### Additional ISO Files +### ISO Files - + -Additional ISO files attached to the virtual machine. +One or more 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" + "isos": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + isos { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" } -] ``` - + @@ -654,20 +666,18 @@ In HCL2: - + -- `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. +- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, + `sata` or `scsi`. + Defaults to `ide`. - `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 +- `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. @@ -679,7 +689,7 @@ In HCL2: - `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 5890d143..5e82e829 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -35,6 +35,40 @@ in the image's Cloud-Init settings for provisioning. ### Required: + + +One or more ISO files attached to the virtual machine. + +JSON Example: + +```json + + "isos": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + isos { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + +``` + + + + - `iso_checksum` (string) - The checksum for the ISO file or virtual hard drive file. The type of @@ -64,6 +98,8 @@ in the image's Cloud-Init settings for provisioning. +See also [ISO Files](#iso-files). + ### Optional: @@ -202,8 +238,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). +- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISO Files](#iso-files). - `vm_interface` (string) - Name of the network interface that Packer gets the VMs IP from. Defaults to the first non loopback interface. @@ -217,29 +253,6 @@ in the image's Cloud-Init settings for provisioning. -- `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_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 `ide2` - -- `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_iso` (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` @@ -261,28 +274,40 @@ in the image's Cloud-Init settings for provisioning. -### Additional ISO Files +### ISO Files - + -Additional ISO files attached to the virtual machine. +One or more 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" + "isos": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + isos { + type = "scsi" + iso_file = "local:iso/virtio-win-0.1.185.iso" + unmount = true + iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" } -] ``` - + @@ -417,20 +442,18 @@ In HCL2: - + -- `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. +- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, + `sata` or `scsi`. + Defaults to `ide`. - `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 +- `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. @@ -442,7 +465,7 @@ In HCL2: - `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/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.go b/builder/proxmox/clone/config.go index 48f2bb94..aa6de538 100644 --- a/builder/proxmox/clone/config.go +++ b/builder/proxmox/clone/config.go @@ -11,7 +11,6 @@ import ( "fmt" "net" "net/netip" - "regexp" "strings" proxmoxcommon "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" @@ -130,53 +129,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%d ipconfig blocks given, but only %d network interfaces defined", len(c.Ipconfigs), len(c.NICs))) } - // each device type has a maximum number of devices that can be attached. - // count disks, additional isos configured for each device type, error if too many. - ideCount := 0 - sataCount := 0 - scsiCount := 0 - virtIOCount := 0 - // count disks - for _, disks := range c.Disks { - switch disks.Type { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - case "virtio": - virtIOCount++ - } - } - // count additional_iso_files devices - for _, iso := range c.AdditionalISOFiles { - // get device type from iso.Device - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(iso.Device) - switch device { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - } - } - // validate device type allocations - if ideCount > 4 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 4 IDE disks and ISOs supported")) - } - if sataCount > 6 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 6 SATA disks and ISOs supported")) - } - if scsiCount > 31 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 31 SCSI disks and ISOs supported")) - } - if virtIOCount > 16 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 16 VirtIO disks supported")) - } - if errs != nil && len(errs.Errors) > 0 { return nil, warnings, errs } diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index e1e28d45..07f34ff3 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:"isos" cty:"isos" hcl:"isos"` + 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())}, + "isos": &hcldec.BlockListSpec{TypeName: "isos", 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..3553bfda --- /dev/null +++ b/builder/proxmox/clone/step_map_source_disks.go @@ -0,0 +1,99 @@ +// 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 + state.Put("config", d) + + 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 aa8d6be3..b409d19d 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 [ISO Files](#iso-files). + ISOs []ISOsConfig `mapstructure:"isos"` // 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,38 +201,46 @@ type Config struct { // Note: this option is for experts only. AdditionalArgs string `mapstructure:"qemu_additional_args"` - // internal use when running a proxmox-iso build. - // proxmox-iso Prepare will set to the device type and index value - // for processing in step_start_vm.go func generateProxmoxDisks - ISOBuilderCDROMDevice string `mapstructure-to-hcl2:",skip"` + // 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. +// One or more 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" +// "isos": [ +// { +// "type": "scsi", +// "iso_file": "local:iso/virtio-win-0.1.185.iso", +// "unmount": true, +// "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" +// } +// ] +// +// ``` +// HCL2 example: +// +// ```hcl +// +// isos { +// 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"` - // 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. - Device string `mapstructure:"device"` + // Bus type and bus index that the ISO will be mounted on. Can be `ide`, + // `sata` or `scsi`. + // Defaults to `ide`. + Type string `mapstructure:"type"` // 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`. @@ -240,7 +248,7 @@ type additionalISOsConfig struct { ISOFile string `mapstructure:"iso_file"` // Proxmox storage pool onto which to upload // the ISO file. - ISOStoragePool string `mapstructure:"iso_storage_pool"` + ISOStoragePool string `mapstructure:"storage_pool"` // Download the ISO directly from the PVE node rather than through Packer. // // Defaults to `false` @@ -252,6 +260,7 @@ type additionalISOsConfig struct { 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"` } @@ -647,8 +656,67 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st log.Printf("OS not set, using default 'other'") c.OS = "other" } + // 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_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 + } + // count isos + switch c.ISOs[idx].Type { + case "ide", "sata", "scsi": + case "": + log.Printf("ISO %d Device not set, using default type '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 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 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 ISO file %s", c.ISOs[idx].Type)) + } + 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" } @@ -689,6 +757,7 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st warnings = append(warnings, "storage_pool_type is deprecated and should be omitted, it will be removed in a later version of the proxmox plugin") } } + 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))) } @@ -757,88 +826,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 strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "virtio") { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("VirtIO is not a supported device type for ISOs")) - } - 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 568cdd5b..cd7c070e 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:"isos" cty:"isos" hcl:"isos"` + 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,59 +239,24 @@ 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())}, + "isos": &hcldec.BlockListSpec{TypeName: "isos", 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"` 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"` + ISOStoragePool *string `mapstructure:"storage_pool" cty:"storage_pool" hcl:"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"` @@ -300,26 +265,26 @@ type FlatadditionalISOsConfig struct { 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}, "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, - "device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false}, + "type": &hcldec.AttrSpec{Name: "type", 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}, + "storage_pool": &hcldec.AttrSpec{Name: "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}, @@ -330,6 +295,41 @@ 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 { diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 12b6ec94..d0bc236f 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -137,24 +137,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,22 +164,22 @@ 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", }, - "iso_file": "local:iso/test.iso", - "iso_url": "http://example.com", - "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "iso_storage_pool": "local", + "iso_file": "local:iso/test.iso", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "storage_pool": "local", }, }, { 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,49 +188,49 @@ func TestAdditionalISOs(t *testing.T) { { name: "cd_files valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "cd_files": []string{ "config_test.go", }, - "iso_storage_pool": "local", + "storage_pool": "local", }, }, { name: "cd_content valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "cd_content": map[string]string{ "test": "config_test.go", }, - "iso_storage_pool": "local", + "storage_pool": "local", }, }, { name: "iso_url valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", - "iso_url": "http://example.com", - "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "iso_storage_pool": "local", + ISOs: map[string]interface{}{ + "type": "ide", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "storage_pool": "local", }, }, { name: "iso_file valid should succeed", expectedToFail: false, - additionalISOFiles: map[string]interface{}{ - "device": "ide1", + ISOs: map[string]interface{}{ + "type": "ide", "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["isos"] = c.ISOs var config Config _, _, err := config.Prepare(&config, cfg) 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 da17edc4..ff739043 100644 --- a/builder/proxmox/common/step_finalize_template_config.go +++ b/builder/proxmox/common/step_finalize_template_config.go @@ -106,23 +106,23 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St } deleteItems := []string{} - if len(c.AdditionalISOFiles) > 0 { - for idx := range c.AdditionalISOFiles { - cdrom := c.AdditionalISOFiles[idx].Device - if c.AdditionalISOFiles[idx].Unmount { + 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 } - if c.AdditionalISOFiles[idx].KeepCDRomDevice { + 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" } } } diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 53fb830a..32707f58 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -8,7 +8,7 @@ import ( "fmt" "log" "reflect" - "regexp" + "slices" "strconv" "strings" @@ -111,6 +111,14 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist kvm = false } + errs, disks := generateProxmoxDisks(c.Disks, c.ISOs, c.CloneSourceDisks) + if errs != nil && len(errs.Errors) > 0 { + log.Print("checkpoint") + state.Put("error", errs) + ui.Error(errs.Error()) + return multistep.ActionHalt + } + config := proxmox.ConfigQemu{ Name: c.VMName, Agent: agent, @@ -131,7 +139,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, c.AdditionalISOFiles, c.ISOBuilderCDROMDevice), + Disks: disks, QemuPCIDevices: generateProxmoxPCIDeviceMap(c.PCIDevices), QemuSerials: generateProxmoxSerials(c.Serials), Scsihw: c.SCSIController, @@ -265,73 +273,20 @@ func generateProxmoxNetworkAdapters(nics []NICConfig) proxmox.QemuDevices { return devs } -func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISOsConfig, bootiso string) *proxmox.QemuStorages { +func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisks []string) (*packersdk.MultiError, *proxmox.QemuStorages) { ideDisks := proxmox.QemuIdeDisks{} sataDisks := proxmox.QemuSataDisks{} scsiDisks := proxmox.QemuScsiDisks{} virtIODisks := proxmox.QemuVirtIODisks{} - // additionalISOsConfig accepts a static device type and index value in Device. - // Disks accept a device type but no index value. - // - // If this is a proxmox-iso build, the boot iso device is mapped after this function (builder/proxmox/iso/builder.go func Create) - // Map Additional ISO files first to ensure they get their assigned device index then proceed with disks in remaining available fields. - if len(additionalISOFiles) > 0 { - for _, iso := range additionalISOFiles { - // IsoFile struct parses the ISO File and Storage Pool as separate fields. - isoFile := strings.Split(iso.ISOFile, ":iso/") - - // define QemuCdRom containing isoFile properties - bootIso := &proxmox.QemuCdRom{ - Iso: &proxmox.IsoFile{ - File: isoFile[1], - Storage: isoFile[0], - }, - } - - // extract device type from ISODevice config value eg. ide from ide2 - // validation of the iso.Device value occurs in builder/proxmox/common/config.go func Prepare - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(iso.Device) - // extract device index from ISODevice config value eg. 2 from ide2 - rb := regexp.MustCompile(`\d+`) - index, _ := strconv.Atoi(rb.FindString(iso.Device)) - - log.Printf("Mapping Additional ISO to %s%d", device, index) - switch device { - case "ide": - dev := proxmox.QemuIdeStorage{ - CdRom: bootIso, - } - reflect. - ValueOf(&ideDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - case "sata": - dev := proxmox.QemuSataStorage{ - CdRom: bootIso, - } - reflect. - ValueOf(&sataDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - case "scsi": - dev := proxmox.QemuScsiStorage{ - CdRom: bootIso, - } - reflect.ValueOf(&scsiDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - } - } - } - ideCount := 0 sataCount := 0 scsiCount := 0 virtIOCount := 0 - // Map Disks + var errs *packersdk.MultiError + + // Map Disks first for idx := range disks { tmpSize, _ := strconv.ParseInt(disks[idx].Size[:len(disks[idx].Size)-1], 10, 0) size := proxmox.QemuDiskSize(0) @@ -361,44 +316,41 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } for { log.Printf("Mapping Disk to ide%d", ideCount) - // to avoid a panic if IDE has too many devices attached, exit the loop when all indexes are occupied + // If IDE has too many devices attached pass an error then exit the loop if ideCount > 3 { - log.Print("No further IDE device indexes available (Max 4 devices).") + 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 } - // 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. - if 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)). - // Return if the field has no device already attached to it - // and not proxmox-iso's configured boot iso device index - IsNil() && bootiso != fmt.Sprintf("ide%d", ideCount) { + + // 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)) { + // 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)). // Assign dev to the Disk_%d field Set(reflect.ValueOf(&dev)) @@ -425,13 +377,10 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO for { log.Printf("Mapping Disk to scsi%d", scsiCount) if scsiCount > 30 { - log.Print("No further SCSI device indexes available (Max 31 devices).") + 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 reflect. - ValueOf(&scsiDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). - IsNil() && bootiso != fmt.Sprintf("scsi%d", scsiCount) { + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("scsi%d", scsiCount)) { reflect. ValueOf(&scsiDisks).Elem(). FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). @@ -456,14 +405,11 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } for { if sataCount > 5 { - log.Print("No further SATA device indexes available (Max 6 devices).") + 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 reflect. - ValueOf(&sataDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", sataCount)). - IsNil() && bootiso != fmt.Sprintf("sata%d", sataCount) { + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("sata%d", sataCount)) { reflect. ValueOf(&sataDisks).Elem(). FieldByName(fmt.Sprintf("Disk_%d", sataCount)). @@ -489,13 +435,10 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO for { log.Printf("Mapping Disk to virtio%d", virtIOCount) if virtIOCount > 15 { - log.Print("No further VirtIO device indexes available (Max 16 devices).") + 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 reflect. - ValueOf(&virtIODisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). - IsNil() { + if !slices.Contains(cloneSourceDisks, fmt.Sprintf("virtio%d", virtIOCount)) { reflect. ValueOf(&virtIODisks).Elem(). FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). @@ -508,7 +451,93 @@ func generateProxmoxDisks(disks []diskConfig, additionalISOFiles []additionalISO } } } - return &proxmox.QemuStorages{ + + // Map ISOs in remaining device indexes + if len(isos) > 0 { + for idx := range isos { + // 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)). + 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)). + 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)). + 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, &proxmox.QemuStorages{ Ide: &ideDisks, Sata: &sataDisks, Scsi: &scsiDisks, diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 57bed9f0..08346218 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -503,8 +503,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { tests := []struct { name string disks []diskConfig - isos []additionalISOsConfig - bootiso string + isos []ISOsConfig expectOutput *proxmox.QemuStorages }{ { @@ -521,8 +520,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - []additionalISOsConfig{}, - "", + []ISOsConfig{}, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -557,8 +555,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - []additionalISOsConfig{}, - "", + []ISOsConfig{}, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -593,8 +590,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - []additionalISOsConfig{}, - "", + []ISOsConfig{}, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -629,8 +625,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - []additionalISOsConfig{}, - "", + []ISOsConfig{}, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -718,8 +713,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { IOThread: true, }, }, - []additionalISOsConfig{}, - "", + []ISOsConfig{}, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -808,7 +802,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, { - "bunch of disks, Additional ISOs and boot iso", + "bunch of disks, Additional ISOs", []diskConfig{ { Type: "ide", @@ -859,13 +853,12 @@ func TestGenerateProxmoxDisks(t *testing.T) { IOThread: true, }, }, - []additionalISOsConfig{ + []ISOsConfig{ { - Device: "sata0", + Type: "sata", ISOFile: "local:iso/test.iso", }, }, - "ide2", &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -886,9 +879,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Discard: false, }, }, - // while ide2 is specified for bootiso, the actual mount of the ISO for proxmox-iso occurs in the its builder func. - // logic in generateProxmoxDisks should only make sure no disks are allocated to ide2 to prevent conflict when allocating disks - Disk_3: &proxmox.QemuIdeStorage{ + Disk_2: &proxmox.QemuIdeStorage{ Disk: &proxmox.QemuIdeDisk{ SizeInKibibytes: 10485760, Storage: "local-lvm", @@ -900,14 +891,6 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, Sata: &proxmox.QemuSataDisks{ Disk_0: &proxmox.QemuSataStorage{ - CdRom: &proxmox.QemuCdRom{ - Iso: &proxmox.IsoFile{ - File: "test.iso", - Storage: "local", - }, - }, - }, - Disk_1: &proxmox.QemuSataStorage{ Disk: &proxmox.QemuSataDisk{ SizeInKibibytes: 11534336, Storage: "local-lvm", @@ -916,7 +899,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Discard: false, }, }, - Disk_2: &proxmox.QemuSataStorage{ + Disk_1: &proxmox.QemuSataStorage{ Disk: &proxmox.QemuSataDisk{ SizeInKibibytes: 13631488, Storage: "local-lvm", @@ -925,6 +908,14 @@ func TestGenerateProxmoxDisks(t *testing.T) { Discard: false, }, }, + Disk_2: &proxmox.QemuSataStorage{ + CdRom: &proxmox.QemuCdRom{ + Iso: &proxmox.IsoFile{ + File: "test.iso", + Storage: "local", + }, + }, + }, }, Scsi: &proxmox.QemuScsiDisks{ Disk_0: &proxmox.QemuScsiStorage{ @@ -945,7 +936,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - devs := generateProxmoxDisks(tt.disks, tt.isos, tt.bootiso) + _, devs := generateProxmoxDisks(tt.disks, tt.isos, nil) 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 d00e5330..3b626ec3 100644 --- a/builder/proxmox/iso/builder.go +++ b/builder/proxmox/iso/builder.go @@ -5,18 +5,11 @@ package proxmoxiso import ( "context" - "fmt" - "log" - "reflect" - "regexp" - "strconv" - "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" ) @@ -43,31 +36,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) 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) @@ -76,59 +45,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 { - // get iso config from state - c := state.Get("iso-config").(*Config) - // IsoFile struct parses the ISO File and Storage Pool as separate fields. - isoFile := strings.Split(state.Get("iso_file").(string), ":iso/") - - // define QemuCdRom struct containing isoFile properties - bootIso := &proxmoxapi.QemuCdRom{ - Iso: &proxmoxapi.IsoFile{ - File: isoFile[1], - Storage: isoFile[0], - }, - } - - // extract device type from ISODevice config value eg. ide from ide2 - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(c.ISODevice) - // extract device index from ISODevice config value eg. 2 from ide2 - rb := regexp.MustCompile(`\d+`) - index, _ := strconv.Atoi(rb.FindString(c.ISODevice)) - - // Assign bootIso QemuCdRom struct to configured ISODevice device type and index - // - // The boot ISO is mapped to a device type and index after Disks and Additional ISO Files - // (builder/proxmox/common/step_start_vm.go func generateProxmoxDisks) - // generateProxmoxDisks contains logic to avoid mapping conflicts - log.Printf("Mapping Boot ISO to %s%d", device, index) - switch device { - case "ide": - dev := proxmoxapi.QemuIdeStorage{ - CdRom: bootIso, - } - reflect. - ValueOf(config.Disks.Ide).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - case "scsi": - dev := proxmoxapi.QemuScsiStorage{ - CdRom: bootIso, - } - reflect. - ValueOf(config.Disks.Scsi).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - case "sata": - dev := proxmoxapi.QemuSataStorage{ - CdRom: bootIso, - } - reflect. - ValueOf(config.Disks.Sata).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", index)). - Set(reflect.ValueOf(&dev)) - } - 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 4f6aec18..77a00650 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -2,51 +2,19 @@ // 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" - "regexp" - "strconv" proxmoxcommon "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"` - - commonsteps.ISOConfig `mapstructure:",squash"` - // 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"` - // 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 `ide2` - ISODevice string `mapstructure:"iso_device"` - // Proxmox storage pool onto which to upload - // the ISO file. - ISOStoragePool string `mapstructure:"iso_storage_pool"` - // Download the ISO directly from the PVE node rather than through Packer. - // - // Defaults to `false` - ISODownloadPVE bool `mapstructure:"iso_download_pve"` - // If true, remove the mounted ISO from the template - // after finishing. Defaults to `false`. - UnmountISO bool `mapstructure:"unmount_iso"` - // 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 } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { @@ -56,113 +24,8 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, merrs) } - // Check ISO config - // Either a pre-uploaded ISO should be referenced in iso_file, OR a URL - // (possibly to a local file) to an ISO file that will be downloaded and - // then uploaded to Proxmox. - // If iso_download_pve is true, iso_url will be downloaded directly to the - // PVE node. - if c.ISOFile != "" { - c.shouldUploadISO = false - } else { - isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.Ctx) - errs = packersdk.MultiErrorAppend(errs, isoErrors...) - warnings = append(warnings, isoWarnings...) - c.shouldUploadISO = true - } - - if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) { - errs = packersdk.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified")) - } - if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified")) - } - - // each device type has a maximum number of devices that can be attached. - // count iso, disks, additional isos configured for each device type, error if too many. - ideCount := 0 - sataCount := 0 - scsiCount := 0 - virtIOCount := 0 - - if c.ISODevice == "" { - // set default ISO boot device ide2 if no value specified - log.Printf("iso_device not set, using default 'ide2'") - c.ISODevice = "ide2" - // Pass c.ISODevice value over to common config to reserve device index - c.ISOBuilderCDROMDevice = "ide2" - ideCount++ - } - - // get device from ISODevice config - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(c.ISODevice) - // get index from ISODevice config - rb := regexp.MustCompile(`\d+`) - _, err := strconv.Atoi(rb.FindString(c.ISODevice)) - if err != nil { - errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device value doesn't contain a valid index number. Expected format is , eg. scsi0. received value: "+c.ISODevice)) - } - // count iso - switch device { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - default: - errs = packersdk.MultiErrorAppend(errs, errors.New("iso_device must be of type ide, sata or scsi. VirtIO not supported for ISO devices")) - } - // Pass c.ISODevice value over to common config to reserve device index - c.ISOBuilderCDROMDevice = c.ISODevice - - // count additional_iso_files devices - for idx, iso := range c.AdditionalISOFiles { - // Prevent a device assignment conflict by ensuring ISOs defined in c.AdditionalISOFiles - // aren't assigned to the same device and index as the boot ISO device. - if iso.Device == c.ISODevice { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("conflicting device assignment between iso_device (%s) and additional_iso_files block %d device", c.ISODevice, idx+1)) - } - // count additional isos - // get device from iso.Device - rd := regexp.MustCompile(`\D+`) - device := rd.FindString(iso.Device) - switch device { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - } - } - - // count disks - for _, disks := range c.Disks { - switch disks.Type { - case "ide": - ideCount++ - case "sata": - sataCount++ - case "scsi": - scsiCount++ - case "virtio": - virtIOCount++ - } - } - // validate device type allocations - if ideCount > 4 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 4 IDE disks and ISOs supported")) - } - if sataCount > 6 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 6 SATA disks and ISOs supported")) - } - if scsiCount > 31 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 31 SCSI disks and ISOs supported")) - } - if virtIOCount > 16 { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("maximum 16 VirtIO disks supported")) + if len(c.ISOs) < 1 { + errs = packersdk.MultiErrorAppend(errs, errors.New("at least one ISO device is required")) } if errs != nil && len(errs.Errors) > 0 { diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index c31b74ae..3074c88d 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -11,126 +11,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 *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"` - ISODevice *string `mapstructure:"iso_device" cty:"iso_device" hcl:"iso_device"` - 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"` - KeepCDRomDevice *bool `mapstructure:"keep_cdrom_device" cty:"keep_cdrom_device" hcl:"keep_cdrom_device"` + 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:"isos" cty:"isos" hcl:"isos"` + 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. @@ -251,20 +240,9 @@ 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())}, + "isos": &hcldec.BlockListSpec{TypeName: "isos", 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}, - "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, - "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, - "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, - "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, - "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, - "iso_device": &hcldec.AttrSpec{Name: "iso_device", 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_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false}, - "keep_cdrom_device": &hcldec.AttrSpec{Name: "keep_cdrom_device", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/proxmox/iso/config_test.go b/builder/proxmox/iso/config_test.go index 633da44b..45705003 100644 --- a/builder/proxmox/iso/config_test.go +++ b/builder/proxmox/iso/config_test.go @@ -34,8 +34,14 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { "storage_pool_type": "lvm" } ], - - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "isos": [ + { + "type": "sata", + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "storage_pool": "local-lvm", + "unmount": "true" + } + ], "http_directory":"config", "boot_wait": "10s", "boot_command": [ @@ -46,7 +52,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\" }}" } @@ -230,6 +235,13 @@ 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", + "isos": []map[string]interface{}{ + { + "type": "sata", + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.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 73caa78d..00000000 --- a/builder/proxmox/iso/step_finalize_iso.go +++ /dev/null @@ -1,71 +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[c.ISODevice] == nil || !strings.Contains(vmParams[c.ISODevice].(string), "media=cdrom") { - err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present, or not a cdrom media", c.ISODevice) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - if c.KeepCDRomDevice { - changes[c.ISODevice] = "none,media=cdrom" - } else { - changes["delete"] = c.ISODevice - } - } - - 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 f0f9edb2..00000000 --- a/builder/proxmox/iso/step_finalize_iso_test.go +++ /dev/null @@ -1,151 +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, - ISODevice: "ide2", - }, - 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": nil, - }, - expectedAction: multistep.ActionContinue, - }, - { - name: "should keep iso device and unmount ISO when configured", - builderConfig: &Config{ - UnmountISO: true, - ISODevice: "ide2", - KeepCDRomDevice: 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..58adad69 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). +- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. + See [ISO Files](#iso-files). - `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/additionalISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx similarity index 50% rename from docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx rename to docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx index a846e63b..dd36594e 100644 --- a/docs-partials/builder/proxmox/common/additionalISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -1,17 +1,15 @@ - + -- `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. +- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, + `sata` or `scsi`. + Defaults to `ide`. - `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 +- `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. @@ -23,4 +21,4 @@ - `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..25bbe742 --- /dev/null +++ b/docs-partials/builder/proxmox/common/ISOsConfig.mdx @@ -0,0 +1,32 @@ + + +One or more ISO files attached to the virtual machine. + +JSON Example: + +```json + + "isos": [ + { + "type": "scsi", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + +``` +HCL2 example: + +```hcl + + isos { + 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.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/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index 821c0f34..08dcd524 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -1,27 +1,4 @@ -- `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_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 `ide2` - -- `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_iso` (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/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..ebfdbef9 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -29,8 +29,12 @@ to you to use it or delete it. ### Required: +@include 'builder/proxmox/common/ISOsConfig.mdx' + @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-required.mdx' +See also [ISO Files](#iso-files). + ### Optional: @include 'builder/proxmox/common/Config-not-required.mdx' @@ -39,9 +43,9 @@ to you to use it or delete it. @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-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' @@ -53,7 +57,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' From a52fd9cf15720fe6b7c84b45f5ce77f2aad04a29 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Thu, 13 Jun 2024 04:19:30 +0000 Subject: [PATCH 07/13] iso: update basic example documentation with iso block --- .web-docs/components/builder/iso/README.md | 12 ++++++++---- docs/builders/iso.mdx | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 5e82e829..c7272a8b 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -1163,7 +1163,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" + isos { + iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } network_adapters { bridge = "vmbr0" model = "virtio" @@ -1176,7 +1178,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}" } @@ -1219,7 +1220,11 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "isos": [ + { + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } + ], "http_directory": "config", "boot_wait": "10s", "boot_command": [ @@ -1228,7 +1233,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/docs/builders/iso.mdx b/docs/builders/iso.mdx index ebfdbef9..62e9388b 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -168,7 +168,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" + isos { + iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } network_adapters { bridge = "vmbr0" model = "virtio" @@ -181,7 +183,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}" } @@ -224,7 +225,11 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", + "isos": [ + { + "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" + } + ], "http_directory": "config", "boot_wait": "10s", "boot_command": [ @@ -233,7 +238,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\" }}" } From 6b51d75444e116210679f2cf3319b45cfce835ac Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Thu, 20 Jun 2024 08:28:47 +0000 Subject: [PATCH 08/13] iso: reintroduce iso config struct for boot iso - to reduce impact on packer configs of previous versions, revert to additional_iso_files map naming - the boot iso is now a config item under the iso builder and prepended to common's ISOs slice for enumeration --- .web-docs/components/builder/clone/README.md | 12 +- .web-docs/components/builder/iso/README.md | 193 ++++-------------- builder/proxmox/clone/config.hcl2spec.go | 4 +- builder/proxmox/common/config.go | 24 +-- builder/proxmox/common/config.hcl2spec.go | 8 +- builder/proxmox/common/config_test.go | 22 +- builder/proxmox/common/step_start_vm.go | 1 - builder/proxmox/iso/builder.go | 9 +- builder/proxmox/iso/config.go | 88 +++++++- builder/proxmox/iso/config.hcl2spec.go | 6 +- builder/proxmox/iso/config_test.go | 20 +- .../proxmox/common/Config-not-required.mdx | 4 +- .../common/ISOsConfig-not-required.mdx | 2 +- .../builder/proxmox/common/ISOsConfig.mdx | 6 +- .../builder/proxmox/iso/Config-required.mdx | 31 +++ docs/builders/iso.mdx | 17 +- 16 files changed, 219 insertions(+), 228 deletions(-) create mode 100644 docs-partials/builder/proxmox/iso/Config-required.mdx diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index cab8d21a..d6fe0340 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`. -- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. - See [ISO Files](#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. @@ -502,13 +502,13 @@ Usage example (JSON): -One or more ISO files attached to the virtual machine. +ISO files attached to the virtual machine. JSON Example: ```json - "isos": [ + "additional_iso_files": [ { "type": "scsi", "iso_file": "local:iso/virtio-win-0.1.185.iso", @@ -522,7 +522,7 @@ HCL2 example: ```hcl - isos { + additional_iso_files { type = "scsi" iso_file = "local:iso/virtio-win-0.1.185.iso" unmount = true @@ -677,7 +677,7 @@ In HCL2: `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. -- `storage_pool` (string) - Proxmox storage pool onto which to upload +- `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. diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index c7272a8b..3bf8859e 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -35,70 +35,38 @@ in the image's Cloud-Init settings for provisioning. ### Required: - - -One or more ISO files attached to the virtual machine. - -JSON Example: - -```json - - "isos": [ - { - "type": "scsi", - "iso_file": "local:iso/virtio-win-0.1.185.iso", - "unmount": true, - "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" - } - ] - -``` -HCL2 example: - -```hcl - - isos { - type = "scsi" - iso_file = "local:iso/virtio-win-0.1.185.iso" - unmount = true - iso_checksum = "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" - } - -``` - - - - - - -- `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. + - +- `iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. + + JSON Example: + + ```json + + "iso": { + "type": "scsi", + "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + "unmount": true, + "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + HCL2 example: + + ```hcl + + 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. + -See also [ISO Files](#iso-files). ### Optional: @@ -238,8 +206,8 @@ See also [ISO Files](#iso-files). - `cloud_init_disk_type` (string) - The type of Cloud-Init disk. Can be `scsi`, `sata`, or `ide` Defaults to `ide`. -- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. - See [ISO Files](#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. @@ -274,17 +242,17 @@ See also [ISO Files](#iso-files). -### ISO Files +### ISOs -One or more ISO files attached to the virtual machine. +ISO files attached to the virtual machine. JSON Example: ```json - "isos": [ + "additional_iso_files": [ { "type": "scsi", "iso_file": "local:iso/virtio-win-0.1.185.iso", @@ -298,7 +266,7 @@ HCL2 example: ```hcl - isos { + additional_iso_files { type = "scsi" iso_file = "local:iso/virtio-win-0.1.185.iso" unmount = true @@ -310,87 +278,6 @@ HCL2 example: - - -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 - - "iso_checksum": "946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2", - "iso_url": "ubuntu.org/.../ubuntu-14.04.1-server-amd64.iso" - -``` - -```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 - - "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" - -``` - -```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" - -``` - -```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 @@ -453,7 +340,7 @@ In HCL2: `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. -- `storage_pool` (string) - Proxmox storage pool onto which to upload +- `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. @@ -1163,7 +1050,7 @@ source "proxmox-iso" "fedora-kickstart" { } http_directory = "config" insecure_skip_tls_verify = true - isos { + iso { iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" } network_adapters { @@ -1220,11 +1107,9 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "isos": [ - { + "iso": { "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" - } - ], + }, "http_directory": "config", "boot_wait": "10s", "boot_command": [ diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index 07f34ff3..e131cb6b 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -117,7 +117,7 @@ type FlatConfig struct { 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:"isos" cty:"isos" hcl:"isos"` + 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"` @@ -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}, - "isos": &hcldec.BlockListSpec{TypeName: "isos", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(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/common/config.go b/builder/proxmox/common/config.go index b409d19d..fff57d94 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -190,8 +190,8 @@ type Config struct { CloudInitDiskType string `mapstructure:"cloud_init_disk_type"` // ISO files attached to the virtual machine. - // See [ISO Files](#iso-files). - ISOs []ISOsConfig `mapstructure:"isos"` + // 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"` @@ -207,13 +207,13 @@ type Config struct { Ctx interpolate.Context `mapstructure-to-hcl2:",skip"` } -// One or more ISO files attached to the virtual machine. +// ISO files attached to the virtual machine. // // JSON Example: // // ```json // -// "isos": [ +// "additional_iso_files": [ // { // "type": "scsi", // "iso_file": "local:iso/virtio-win-0.1.185.iso", @@ -227,7 +227,7 @@ type Config struct { // // ```hcl // -// isos { +// additional_iso_files { // type = "scsi" // iso_file = "local:iso/virtio-win-0.1.185.iso" // unmount = true @@ -248,7 +248,7 @@ type ISOsConfig struct { ISOFile string `mapstructure:"iso_file"` // Proxmox storage pool onto which to upload // the ISO file. - ISOStoragePool string `mapstructure:"storage_pool"` + ISOStoragePool string `mapstructure:"iso_storage_pool"` // Download the ISO directly from the PVE node rather than through Packer. // // Defaults to `false` @@ -665,7 +665,7 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st if c.ISOs[idx].ISOFile != "" { c.ISOs[idx].ShouldUploadISO = false } else { - c.ISOs[idx].DownloadPathKey = "downloaded_iso_path_" + strconv.Itoa(idx) + 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...) @@ -676,14 +676,14 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } c.ISOs[idx].ShouldUploadISO = true } - // count isos + // validate device type, assign if unset switch c.ISOs[idx].Type { case "ide", "sata", "scsi": case "": - log.Printf("ISO %d Device not set, using default type 'ide'", idx) + 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 for ISO devices")) + 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 == "" { @@ -691,7 +691,7 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } } if len(c.ISOs[idx].ISOUrls) != 0 && c.ISOs[idx].ISOStoragePool == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url in an isos block, iso_storage_pool must also be specified")) + 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 @@ -705,7 +705,7 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st 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 ISO file %s", c.ISOs[idx].Type)) + 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")) diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index cd7c070e..2a8fad88 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -116,7 +116,7 @@ type FlatConfig struct { 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:"isos" cty:"isos" hcl:"isos"` + 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"` } @@ -239,7 +239,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}, - "isos": &hcldec.BlockListSpec{TypeName: "isos", Nested: hcldec.ObjectSpec((*FlatISOsConfig)(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}, } @@ -256,7 +256,7 @@ type FlatISOsConfig struct { TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` Type *string `mapstructure:"type" cty:"type" hcl:"type"` ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` - ISOStoragePool *string `mapstructure:"storage_pool" cty:"storage_pool" hcl:"storage_pool"` + 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"` @@ -284,7 +284,7 @@ func (*FlatISOsConfig) HCL2Spec() map[string]hcldec.Spec { "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, - "storage_pool": &hcldec.AttrSpec{Name: "storage_pool", 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}, diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index d0bc236f..3d08b639 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -169,10 +169,10 @@ func TestISOs(t *testing.T) { "cd_files": []string{ "config_test.go", }, - "iso_file": "local:iso/test.iso", - "iso_url": "http://example.com", - "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "storage_pool": "local", + "iso_file": "local:iso/test.iso", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "iso_storage_pool": "local", }, }, { @@ -193,7 +193,7 @@ func TestISOs(t *testing.T) { "cd_files": []string{ "config_test.go", }, - "storage_pool": "local", + "iso_storage_pool": "local", }, }, { @@ -204,17 +204,17 @@ func TestISOs(t *testing.T) { "cd_content": map[string]string{ "test": "config_test.go", }, - "storage_pool": "local", + "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", - "storage_pool": "local", + "type": "ide", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "iso_storage_pool": "local", }, }, { @@ -230,7 +230,7 @@ func TestISOs(t *testing.T) { for _, c := range isotests { t.Run(c.name, func(t *testing.T) { cfg := mandatoryConfig(t) - cfg["isos"] = c.ISOs + cfg["additional_iso_files"] = c.ISOs var config Config _, _, err := config.Prepare(&config, cfg) diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 32707f58..3fc85708 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -113,7 +113,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist errs, disks := generateProxmoxDisks(c.Disks, c.ISOs, c.CloneSourceDisks) if errs != nil && len(errs.Errors) > 0 { - log.Print("checkpoint") state.Put("error", errs) ui.Error(errs.Error()) return multistep.ActionHalt diff --git a/builder/proxmox/iso/builder.go b/builder/proxmox/iso/builder.go index 3b626ec3..83906f60 100644 --- a/builder/proxmox/iso/builder.go +++ b/builder/proxmox/iso/builder.go @@ -29,10 +29,15 @@ 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{} diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go index 77a00650..d92a2095 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -8,13 +8,44 @@ 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" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) type Config struct { - proxmoxcommon.Config `mapstructure:",squash"` + common.Config `mapstructure:",squash"` + + // Boot ISO attached to the virtual machine. + // + // JSON Example: + // + // ```json + // + // "iso": { + // "type": "scsi", + // "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + // "unmount": true, + // "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + // } + // + // ``` + // HCL2 example: + // + // ```hcl + // + // 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:"iso" required:"true"` } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { @@ -24,8 +55,57 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, merrs) } - if len(c.ISOs) < 1 { - errs = packersdk.MultiErrorAppend(errs, errors.New("at least one ISO device is required")) + // 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 c.BootISO.ISOFile != "" { + c.BootISO.ShouldUploadISO = false + } else { + c.BootISO.DownloadPathKey = "downloaded_iso_path_0" + 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 + } + // validate device type, assign if unset + switch c.BootISO.Type { + case "ide", "sata", "scsi": + case "": + log.Print("iso device type not set, using default type 'ide'") + c.BootISO.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.BootISO.CDFiles) > 0 || len(c.BootISO.CDContent) > 0 { + if c.BootISO.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.BootISO.ISOUrls) != 0 && c.BootISO.ISOStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New("when specifying iso_url in an 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 iso")) + } + 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 3074c88d..fcd2943d 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -117,9 +117,10 @@ type FlatConfig struct { 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:"isos" cty:"isos" hcl:"isos"` + 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"` + BootISO *proxmox.FlatISOsConfig `mapstructure:"iso" required:"true" cty:"iso" hcl:"iso"` } // FlatMapstructure returns a new FlatConfig. @@ -240,9 +241,10 @@ 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}, - "isos": &hcldec.BlockListSpec{TypeName: "isos", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(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": &hcldec.BlockSpec{TypeName: "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 45705003..5aa591de 100644 --- a/builder/proxmox/iso/config_test.go +++ b/builder/proxmox/iso/config_test.go @@ -34,14 +34,12 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { "storage_pool_type": "lvm" } ], - "isos": [ - { + "iso": { "type": "sata", "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", - "storage_pool": "local-lvm", + "iso_storage_pool": "local-lvm", "unmount": "true" - } - ], + }, "http_directory":"config", "boot_wait": "10s", "boot_command": [ @@ -235,13 +233,11 @@ func mandatoryConfig(t *testing.T) map[string]interface{} { "password": "supersecret", "node": "my-proxmox", "ssh_username": "root", - "isos": []map[string]interface{}{ - { - "type": "sata", - "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", - "storage_pool": "local-lvm", - "unmount": "true", - }, + "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/docs-partials/builder/proxmox/common/Config-not-required.mdx b/docs-partials/builder/proxmox/common/Config-not-required.mdx index 58adad69..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`. -- `isos` ([]ISOsConfig) - ISO files attached to the virtual machine. - See [ISO Files](#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 index dd36594e..dad71ca0 100644 --- a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -9,7 +9,7 @@ `local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`. Either `iso_file` OR `iso_url` must be specifed. -- `storage_pool` (string) - Proxmox storage pool onto which to upload +- `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. diff --git a/docs-partials/builder/proxmox/common/ISOsConfig.mdx b/docs-partials/builder/proxmox/common/ISOsConfig.mdx index 25bbe742..2a369cd2 100644 --- a/docs-partials/builder/proxmox/common/ISOsConfig.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig.mdx @@ -1,12 +1,12 @@ -One or more ISO files attached to the virtual machine. +ISO files attached to the virtual machine. JSON Example: ```json - "isos": [ + "additional_iso_files": [ { "type": "scsi", "iso_file": "local:iso/virtio-win-0.1.185.iso", @@ -20,7 +20,7 @@ HCL2 example: ```hcl - isos { + additional_iso_files { type = "scsi" iso_file = "local:iso/virtio-win-0.1.185.iso" unmount = true 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..78c83725 --- /dev/null +++ b/docs-partials/builder/proxmox/iso/Config-required.mdx @@ -0,0 +1,31 @@ + + +- `iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. + + JSON Example: + + ```json + + "iso": { + "type": "scsi", + "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", + "unmount": true, + "iso_checksum": "sha512:33c08e56c83d13007e4a5511b9bf2c4926c4aa12fd5dd56d493c0653aecbab380988c5bf1671dbaea75c582827797d98c4a611f7fb2b131fbde2c677d5258ec9" + } + + ``` + HCL2 example: + + ```hcl + + 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/iso.mdx b/docs/builders/iso.mdx index 62e9388b..99ca64b4 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -29,11 +29,7 @@ to you to use it or delete it. ### Required: -@include 'builder/proxmox/common/ISOsConfig.mdx' - -@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-required.mdx' - -See also [ISO Files](#iso-files). +@include 'builder/proxmox/iso/Config-required.mdx' ### Optional: @@ -43,11 +39,10 @@ See also [ISO Files](#iso-files). @include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-not-required.mdx' -### ISO Files +### ISOs @include 'builder/proxmox/common/ISOsConfig.mdx' -@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig.mdx' #### Required @@ -168,7 +163,7 @@ source "proxmox-iso" "fedora-kickstart" { } http_directory = "config" insecure_skip_tls_verify = true - isos { + iso { iso_file = "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" } network_adapters { @@ -225,11 +220,9 @@ build { "pre_enrolled_keys": true, "efi_type": "4m" }, - "isos": [ - { + "iso": { "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso" - } - ], + }, "http_directory": "config", "boot_wait": "10s", "boot_command": [ From e635f0e6ce8654dbf08b46fab07c578ec4e53141 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Thu, 20 Jun 2024 09:24:19 +0000 Subject: [PATCH 09/13] common: update documentation of ISOsConfig Type field --- .web-docs/components/builder/clone/README.md | 9 ++++++--- .web-docs/components/builder/iso/README.md | 9 ++++++--- builder/proxmox/common/config.go | 9 ++++++--- .../builder/proxmox/common/ISOsConfig-not-required.mdx | 9 ++++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index d6fe0340..f40faa5b 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -668,9 +668,12 @@ In HCL2: -- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, - `sata` or `scsi`. - Defaults to `ide`. +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + + In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order + they are configured, using free bus indexes after disks are attached. + Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM + as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) - `iso_file` (string) - Path to the ISO file to boot from, expressed as a proxmox datastore path, for example diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 3bf8859e..ab2685ac 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -331,9 +331,12 @@ HCL2 example: -- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, - `sata` or `scsi`. - Defaults to `ide`. +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + + In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order + they are configured, using free bus indexes after disks are attached. + Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM + as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) - `iso_file` (string) - Path to the ISO file to boot from, expressed as a proxmox datastore path, for example diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index fff57d94..8de53487 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -237,9 +237,12 @@ type Config struct { // ``` type ISOsConfig struct { commonsteps.ISOConfig `mapstructure:",squash"` - // Bus type and bus index that the ISO will be mounted on. Can be `ide`, - // `sata` or `scsi`. - // Defaults to `ide`. + // Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + // + // In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order + // they are configured, using free bus indexes after disks are attached. + // Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM + // as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) Type string `mapstructure:"type"` // Path to the ISO file to boot from, expressed as a // proxmox datastore path, for example diff --git a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx index dad71ca0..2600d41e 100644 --- a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -1,8 +1,11 @@ -- `type` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ide`, - `sata` or `scsi`. - Defaults to `ide`. +- `type` (string) - Bus type that the ISO will be mounted on. Can be `ide`, `sata` or `scsi`. Defaults to `ide`. + + In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order + they are configured, using free bus indexes after disks are attached. + Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM + as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) - `iso_file` (string) - Path to the ISO file to boot from, expressed as a proxmox datastore path, for example From 83708737d33d56f04bb69312166e2aa408202876 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Mon, 15 Jul 2024 11:32:14 +0000 Subject: [PATCH 10/13] common: restore default disk backup behaviour In testing it was observed that the upstream disk API changes are excluding disks from Proxmox backup jobs by default (setting backup=0). This commit reinstates the behaviour of previous versions of this packer-plugin-proxmox where disks created by Packer are enabled for backup by default. A new ExcludeFromBackup disk field has been added to opt out of this behaviour. --- .web-docs/components/builder/clone/README.md | 3 + .web-docs/components/builder/iso/README.md | 3 + builder/proxmox/common/config.go | 3 + builder/proxmox/common/config.hcl2spec.go | 42 +++--- builder/proxmox/common/step_start_vm.go | 8 ++ builder/proxmox/common/step_start_vm_test.go | 123 +++++++++++++----- .../common/diskConfig-not-required.mdx | 3 + 7 files changed, 132 insertions(+), 53 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index f40faa5b..48bae1b0 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -447,6 +447,9 @@ Example: - `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/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index ab2685ac..7e0c679a 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -587,6 +587,9 @@ Example: - `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/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 8de53487..696a2a50 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -362,6 +362,9 @@ type diskConfig struct { // 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) diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index 2a8fad88..93dd71df 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -333,16 +333,17 @@ func (*FlatNICConfig) HCL2Spec() map[string]hcldec.Spec { // 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"` - AsyncIO *string `mapstructure:"asyncio" cty:"asyncio" hcl:"asyncio"` - 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. @@ -357,16 +358,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}, - "asyncio": &hcldec.AttrSpec{Name: "asyncio", Type: cty.String, 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/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 3fc85708..8775a98c 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -299,6 +299,10 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk case "K": size = proxmox.QemuDiskSize(tmpSize) } + backup := true + if disks[idx].ExcludeFromBackup { + backup = false + } switch disks[idx].Type { case "ide": @@ -311,6 +315,7 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, + Backup: backup, }, } for { @@ -371,6 +376,7 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, IOThread: disks[idx].IOThread, + Backup: backup, }, } for { @@ -400,6 +406,7 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, EmulateSSD: disks[idx].SSD, + Backup: backup, }, } for { @@ -429,6 +436,7 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), Discard: disks[idx].Discard, IOThread: disks[idx].IOThread, + Backup: backup, }, } for { diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 08346218..2c3a2731 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -510,14 +510,15 @@ func TestGenerateProxmoxDisks(t *testing.T) { "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{}, @@ -535,6 +536,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Discard: false, EmulateSSD: false, IOThread: false, + Backup: true, }, }, }, @@ -545,14 +547,15 @@ 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{}, @@ -570,6 +573,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Discard: false, EmulateSSD: false, IOThread: true, + Backup: true, }, }, }, @@ -580,14 +584,15 @@ 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{}, @@ -605,6 +610,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, @@ -614,15 +620,16 @@ func TestGenerateProxmoxDisks(t *testing.T) { "asyncio is native", []diskConfig{ { - Type: "virtio", - StoragePool: "local-lvm", - Size: "10G", - CacheMode: "none", - DiskFormat: "qcow2", - AsyncIO: "native", - IOThread: true, - Discard: false, - SSD: false, + Type: "virtio", + StoragePool: "local-lvm", + Size: "10G", + CacheMode: "none", + DiskFormat: "qcow2", + AsyncIO: "native", + IOThread: true, + Discard: false, + SSD: false, + ExcludeFromBackup: false, }, }, []ISOsConfig{}, @@ -640,6 +647,42 @@ func TestGenerateProxmoxDisks(t *testing.T) { 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{}, + &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", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + Backup: false, }, }, }, @@ -723,6 +766,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuIdeStorage{ @@ -732,6 +776,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, }, @@ -743,6 +788,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuSataStorage{ @@ -752,6 +798,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, }, @@ -764,6 +811,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, Disk_1: &proxmox.QemuScsiStorage{ @@ -774,6 +822,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, @@ -786,6 +835,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, Disk_1: &proxmox.QemuVirtIOStorage{ @@ -796,6 +846,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, @@ -868,6 +919,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuIdeStorage{ @@ -877,6 +929,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_2: &proxmox.QemuIdeStorage{ @@ -886,6 +939,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, }, @@ -897,6 +951,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_1: &proxmox.QemuSataStorage{ @@ -906,6 +961,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Cache: proxmox.QemuDiskCache("none"), Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, + Backup: true, }, }, Disk_2: &proxmox.QemuSataStorage{ @@ -926,6 +982,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { Format: proxmox.QemuDiskFormat("qcow2"), Discard: false, IOThread: true, + Backup: true, }, }, }, diff --git a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx index bbd36df3..7e880550 100644 --- a/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/diskConfig-not-required.mdx @@ -28,6 +28,9 @@ - `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) From 80edb15ad926d2ca1dcac93fd1491c8bead22e3b Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Wed, 18 Sep 2024 11:28:47 +0000 Subject: [PATCH 11/13] add backwards compatibility support for ISOs - Add conversion handling, tests, UI deprecation warnings for deprecated iso builder config options - Add conversion handling, tests, UI deprecation warnings for `device` field in ISOsConfig struct - Add logic to handle statically assigned ISO devices from `device` field - Add default static mapping of boot ISO to ide2 if boot_iso `type` undefined in packer config - Update documentation --- .web-docs/components/builder/clone/README.md | 14 +- .web-docs/components/builder/iso/README.md | 38 ++- builder/proxmox/common/config.go | 60 +++- builder/proxmox/common/config.hcl2spec.go | 4 + builder/proxmox/common/config_test.go | 41 +++ builder/proxmox/common/step_start_vm.go | 316 ++++++++++++------ builder/proxmox/common/step_start_vm_test.go | 2 +- builder/proxmox/iso/config.go | 80 ++++- builder/proxmox/iso/config.hcl2spec.go | 22 +- builder/proxmox/iso/config_test.go | 56 +++- .../common/ISOsConfig-not-required.mdx | 14 +- .../proxmox/iso/Config-not-required.mdx | 18 + .../builder/proxmox/iso/Config-required.mdx | 6 +- 13 files changed, 524 insertions(+), 147 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 48bae1b0..78788f5a 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -671,12 +671,16 @@ In HCL2: +- `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 assignment (next available 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`. - - In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order - they are configured, using free bus indexes after disks are attached. - Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM - as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) + +- `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 diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 7e0c679a..b4f3edea 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -37,13 +37,13 @@ in the image's Cloud-Init settings for provisioning. -- `iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. +- `boot_iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. JSON Example: ```json - "iso": { + "boot_iso": { "type": "scsi", "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", "unmount": true, @@ -55,7 +55,7 @@ in the image's Cloud-Init settings for provisioning. ```hcl - iso { + boot_iso { type = "scsi" iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" unmount = true @@ -221,6 +221,24 @@ in the image's Cloud-Init settings for provisioning. +- `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) - 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) - 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) - 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`. @@ -331,12 +349,16 @@ HCL2 example: +- `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 assignment (next available 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`. - - In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order - they are configured, using free bus indexes after disks are attached. - Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM - as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) + +- `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 diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 696a2a50..50147bf3 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -237,13 +237,17 @@ type Config 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. + // Defaulted to `ide3` in versions up to v1.8, now defaults to dynamic assignment (next available 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`. - // - // In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order - // they are configured, using free bus indexes after disks are attached. - // Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM - // as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) 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`. @@ -682,6 +686,52 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st } 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": diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index 93dd71df..11ce4605 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -254,7 +254,9 @@ type FlatISOsConfig struct { 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"` @@ -282,7 +284,9 @@ func (*FlatISOsConfig) HCL2Spec() map[string]hcldec.Spec { "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, + "device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false}, "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}, diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 3d08b639..47803fc7 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" @@ -244,7 +245,47 @@ func TestISOs(t *testing.T) { } }) } +} +func TestDeprecatedISOOptionsAreConverted(t *testing.T) { + isotests := []struct { + name string + ISOs map[string]interface{} + }{ + { + // Ensure deprecated device field is converted + name: "device should be converted to type and index", + ISOs: map[string]interface{}{ + "device": "ide3", + "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 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) + } + }) + } } func TestRng0(t *testing.T) { diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 8775a98c..85f66d3d 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -111,12 +111,17 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist kvm = false } - errs, disks := generateProxmoxDisks(c.Disks, c.ISOs, c.CloneSourceDisks) + 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, @@ -272,11 +277,17 @@ func generateProxmoxNetworkAdapters(nics []NICConfig) proxmox.QemuDevices { return devs } -func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisks []string) (*packersdk.MultiError, *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 @@ -284,8 +295,104 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk 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 Disks first + // Map Hard Disks for idx := range disks { tmpSize, _ := strconv.ParseInt(disks[idx].Size[:len(disks[idx].Size)-1], 10, 0) size := proxmox.QemuDiskSize(0) @@ -327,36 +434,15 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk } // 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)) { - // 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. + 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. - // 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)). - // Assign dev to the Disk_%d field Set(reflect.ValueOf(&dev)) ideCount++ break @@ -385,7 +471,10 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk 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)) { + 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)). @@ -415,7 +504,10 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk break } log.Printf("Mapping Disk to sata%d", sataCount) - if !slices.Contains(cloneSourceDisks, fmt.Sprintf("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)). @@ -445,7 +537,10 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk 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)) { + 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)). @@ -459,97 +554,104 @@ func generateProxmoxDisks(disks []diskConfig, isos []ISOsConfig, cloneSourceDisk } } - // Map ISOs in remaining device indexes + // Map ISOs without static mappings in remaining device indexes if len(isos) > 0 { for idx := range isos { - // 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, + // 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], + }, } - 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 + + switch isos[idx].Type { + case "ide": + dev := proxmox.QemuIdeStorage{ + CdRom: cdrom, } - if !slices.Contains(cloneSourceDisks, fmt.Sprintf("ide%d", ideCount)) { - reflect. - ValueOf(&ideDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", ideCount)). - Set(reflect.ValueOf(&dev)) - isos[idx].AssignedDeviceIndex = fmt.Sprintf("ide%d", ideCount) + 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++ - 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 + case "sata": + dev := proxmox.QemuSataStorage{ + CdRom: cdrom, } - 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)). - Set(reflect.ValueOf(&dev)) - isos[idx].AssignedDeviceIndex = fmt.Sprintf("sata%d", 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 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++ - 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 + case "scsi": + dev := proxmox.QemuScsiStorage{ + CdRom: cdrom, } - if !slices.Contains(cloneSourceDisks, fmt.Sprintf("scsi%d", scsiCount)) { - reflect. - ValueOf(&scsiDisks).Elem(). - FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). - Set(reflect.ValueOf(&dev)) - isos[idx].AssignedDeviceIndex = fmt.Sprintf("scsi%d", scsiCount) + 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++ - break } - log.Printf("scsi%d occupied, trying next device index", scsiCount) - scsiCount++ } } } } - return errs, &proxmox.QemuStorages{ - Ide: &ideDisks, - Sata: &sataDisks, - Scsi: &scsiDisks, - VirtIO: &virtIODisks, - } + 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 2c3a2731..69cb0e92 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -993,7 +993,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, devs := generateProxmoxDisks(tt.disks, tt.isos, nil) + _, _, devs := generateProxmoxDisks(tt.disks, tt.isos, nil) assert.Equal(t, devs, tt.expectOutput) }) } diff --git a/builder/proxmox/iso/config.go b/builder/proxmox/iso/config.go index d92a2095..7bd610f3 100644 --- a/builder/proxmox/iso/config.go +++ b/builder/proxmox/iso/config.go @@ -12,19 +12,40 @@ import ( "log" 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 { 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"` // Boot ISO attached to the virtual machine. // // JSON Example: // // ```json // - // "iso": { + // "boot_iso": { // "type": "scsi", // "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", // "unmount": true, @@ -36,7 +57,7 @@ type Config struct { // // ```hcl // - // iso { + // boot_iso { // type = "scsi" // iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" // unmount = true @@ -45,7 +66,7 @@ type Config struct { // // ``` // See [ISOs](#isos) for additional options. - BootISO common.ISOsConfig `mapstructure:"iso" required:"true"` + BootISO common.ISOsConfig `mapstructure:"boot_iso" required:"true"` } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { @@ -55,6 +76,45 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend(errs, merrs) } + // 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 @@ -62,7 +122,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { if c.BootISO.ISOFile != "" { c.BootISO.ShouldUploadISO = false } else { - c.BootISO.DownloadPathKey = "downloaded_iso_path_0" + 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...) @@ -74,21 +134,23 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { c.BootISO.ShouldUploadISO = true } // 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("iso device type not set, using default type 'ide'") + 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("storage_pool not set for storage of generated ISO from cd_files or cd_content")) + 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 an iso block, iso_storage_pool must also be specified")) + 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 @@ -102,7 +164,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { 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 iso")) + 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.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")) diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index fcd2943d..3e77a919 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -120,7 +120,16 @@ type FlatConfig struct { 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"` - BootISO *proxmox.FlatISOsConfig `mapstructure:"iso" required:"true" cty:"iso" hcl:"iso"` + 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. @@ -244,7 +253,16 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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": &hcldec.BlockSpec{TypeName: "iso", Nested: hcldec.ObjectSpec((*proxmox.FlatISOsConfig)(nil).HCL2Spec())}, + "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, + "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, + "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, + "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, + "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, + "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, + "iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false}, + "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 5aa591de..f2540087 100644 --- a/builder/proxmox/iso/config_test.go +++ b/builder/proxmox/iso/config_test.go @@ -34,7 +34,7 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { "storage_pool_type": "lvm" } ], - "iso": { + "boot_iso": { "type": "sata", "iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso", "iso_storage_pool": "local-lvm", @@ -117,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 @@ -233,7 +285,7 @@ func mandatoryConfig(t *testing.T) map[string]interface{} { "password": "supersecret", "node": "my-proxmox", "ssh_username": "root", - "iso": map[string]interface{}{ + "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", diff --git a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx index 2600d41e..7a0a2057 100644 --- a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -1,11 +1,15 @@ +- `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 assignment (next available 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`. - - In v1.9 bus indexes are no longer accepted for ISOs. ISOs are now attached to VMs in the order - they are configured, using free bus indexes after disks are attached. - Example: if two Disks and one ISO are defined as type `sata`, the disks will be attached to the VM - as `sata0`, `sata1`, and the ISO will be mapped to `sata2` (the next free device index) + +- `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 diff --git a/docs-partials/builder/proxmox/iso/Config-not-required.mdx b/docs-partials/builder/proxmox/iso/Config-not-required.mdx index 08dcd524..57cf1f70 100644 --- a/docs-partials/builder/proxmox/iso/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-not-required.mdx @@ -1,4 +1,22 @@ +- `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) - 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) - 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) - 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 index 78c83725..a7e80c86 100644 --- a/docs-partials/builder/proxmox/iso/Config-required.mdx +++ b/docs-partials/builder/proxmox/iso/Config-required.mdx @@ -1,12 +1,12 @@ -- `iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. +- `boot_iso` (common.ISOsConfig) - Boot ISO attached to the virtual machine. JSON Example: ```json - "iso": { + "boot_iso": { "type": "scsi", "iso_file": "local:iso/debian-12.5.0-amd64-netinst.iso", "unmount": true, @@ -18,7 +18,7 @@ ```hcl - iso { + boot_iso { type = "scsi" iso_file = "local:iso/debian-12.5.0-amd64-netinst.iso" unmount = true From b51bacaf6c8de49c5f07ea5184d4e5d06d487920 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Tue, 24 Sep 2024 01:35:02 +0000 Subject: [PATCH 12/13] Improve tests, update doc - Added test for overallocation of storage - Added additional tests for ISO `device` field conversion - Added missing default bus detail to `device` field documentation --- .web-docs/components/builder/clone/README.md | 2 +- .web-docs/components/builder/iso/README.md | 2 +- .../proxmox/clone/step_map_source_disks.go | 1 - builder/proxmox/common/config.go | 2 +- builder/proxmox/common/config_test.go | 52 +++++++++- builder/proxmox/common/step_start_vm_test.go | 95 +++++++++++++++++-- .../common/ISOsConfig-not-required.mdx | 2 +- 7 files changed, 140 insertions(+), 16 deletions(-) diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 78788f5a..986ffb2b 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -676,7 +676,7 @@ In HCL2: `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 assignment (next available bus index after hard disks are allocated) + 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`. diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index b4f3edea..b9baa7a0 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -354,7 +354,7 @@ HCL2 example: `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 assignment (next available bus index after hard disks are allocated) + 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`. diff --git a/builder/proxmox/clone/step_map_source_disks.go b/builder/proxmox/clone/step_map_source_disks.go index 3553bfda..6e73fd4a 100644 --- a/builder/proxmox/clone/step_map_source_disks.go +++ b/builder/proxmox/clone/step_map_source_disks.go @@ -91,7 +91,6 @@ func (s *StepMapSourceDisks) Run(ctx context.Context, state multistep.StateBag) // store discovered disks in common config d := state.Get("config").(*proxmox.Config) d.CloneSourceDisks = sourceDisks - state.Put("config", d) return multistep.ActionContinue } diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 50147bf3..f8104fe8 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -242,7 +242,7 @@ type ISOsConfig struct { // `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 assignment (next available bus index after hard disks are allocated) + // 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"` diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 47803fc7..ad58e22b 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -249,14 +249,47 @@ func TestISOs(t *testing.T) { func TestDeprecatedISOOptionsAreConverted(t *testing.T) { isotests := []struct { - name string - ISOs map[string]interface{} + name string + expectedToFail bool + ISOs map[string]interface{} }{ { - // Ensure deprecated device field is converted - name: "device should be converted to type and index", + name: "cd_files valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ + "device": "ide1", + "cd_files": []string{ + "config_test.go", + }, + "iso_storage_pool": "local", + }, + }, + { + name: "cd_content valid should succeed", + expectedToFail: false, ISOs: map[string]interface{}{ - "device": "ide3", + "device": "ide1", + "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{}{ + "device": "ide1", + "iso_url": "http://example.com", + "iso_checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "iso_storage_pool": "local", + }, + }, + { + name: "iso_file valid should succeed", + expectedToFail: false, + ISOs: map[string]interface{}{ + "device": "ide1", "iso_file": "local:iso/test.iso", }, }, @@ -284,6 +317,15 @@ func TestDeprecatedISOOptionsAreConverted(t *testing.T) { if config.ISOs[0].Index != index { t.Errorf("Expected device to be converted to index %s", index) } + + if c.expectedToFail == true && err == nil { + t.Error("expected config preparation to fail, but no error occured") + } + + if c.expectedToFail == false && err != nil { + t.Errorf("expected config preparation to succeed, but %s", err.Error()) + } + }) } } diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 69cb0e92..602e4178 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -501,10 +501,12 @@ func TestStartVM_AssertInitialQuemuConfig(t *testing.T) { func TestGenerateProxmoxDisks(t *testing.T) { tests := []struct { - name string - disks []diskConfig - isos []ISOsConfig - expectOutput *proxmox.QemuStorages + name string + disks []diskConfig + isos []ISOsConfig + clonesourcedisks []string + expectedToFail bool + expectOutput *proxmox.QemuStorages }{ { "plain config, no special option set", @@ -522,6 +524,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -559,6 +563,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -596,6 +602,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -633,6 +641,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -669,6 +679,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{}, Sata: &proxmox.QemuSataDisks{}, @@ -688,6 +700,62 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, }, + { + "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{}, + }, { "bunch of disks, should be defined in the discovery order", []diskConfig{ @@ -757,6 +825,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, []ISOsConfig{}, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -910,6 +980,8 @@ func TestGenerateProxmoxDisks(t *testing.T) { ISOFile: "local:iso/test.iso", }, }, + []string{}, + false, &proxmox.QemuStorages{ Ide: &proxmox.QemuIdeDisks{ Disk_0: &proxmox.QemuIdeStorage{ @@ -993,8 +1065,19 @@ func TestGenerateProxmoxDisks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, _, devs := generateProxmoxDisks(tt.disks, tt.isos, nil) - 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/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx index 7a0a2057..951d37a4 100644 --- a/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx +++ b/docs-partials/builder/proxmox/common/ISOsConfig-not-required.mdx @@ -5,7 +5,7 @@ `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 assignment (next available bus index after hard disks are allocated) + 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`. From bfa19d5a058bdb3699ca65eee9ddc414c68a77a3 Mon Sep 17 00:00:00 2001 From: Martin Pywell Date: Tue, 24 Sep 2024 23:39:49 +0000 Subject: [PATCH 13/13] test all buses for overallocation, cleanup test bool conditions --- builder/proxmox/common/config_test.go | 8 +- builder/proxmox/common/step_start_vm_test.go | 228 +++++++++++++++++++ 2 files changed, 232 insertions(+), 4 deletions(-) diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index ad58e22b..50cbc39d 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -236,11 +236,11 @@ func TestISOs(t *testing.T) { var config Config _, _, err := config.Prepare(&config, cfg) - 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()) } }) @@ -318,11 +318,11 @@ func TestDeprecatedISOOptionsAreConverted(t *testing.T) { 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()) } diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 602e4178..a228778b 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -700,6 +700,28 @@ func TestGenerateProxmoxDisks(t *testing.T) { }, }, }, + { + "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{ @@ -756,6 +778,212 @@ func TestGenerateProxmoxDisks(t *testing.T) { 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{