diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 46e0ae19..b148a3d3 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -221,6 +221,8 @@ boot time. - `rng0` (rng0Config) - Configure Random Number Generator via VirtIO. See [VirtIO RNG device](#virtio-rng-device) +- `tpm_config` (tpmConfig) - Set the tpmstate storage options. See [TPM Config](#tpm-config). + - `vga` (vgaConfig) - The graphics adapter to use. See [VGA Config](#vga-config). - `network_adapters` ([]NICConfig) - The network adapter to use. See [Network Adapters](#network-adapters) diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 53305097..4c7e8f97 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -152,6 +152,8 @@ in the image's Cloud-Init settings for provisioning. - `rng0` (rng0Config) - Configure Random Number Generator via VirtIO. See [VirtIO RNG device](#virtio-rng-device) +- `tpm_config` (tpmConfig) - Set the tpmstate storage options. See [TPM Config](#tpm-config). + - `vga` (vgaConfig) - The graphics adapter to use. See [VGA Config](#vga-config). - `network_adapters` ([]NICConfig) - The network adapter to use. See [Network Adapters](#network-adapters) diff --git a/builder/proxmox/clone/builder.go b/builder/proxmox/clone/builder.go index 6cbd8681..ac46376c 100644 --- a/builder/proxmox/clone/builder.go +++ b/builder/proxmox/clone/builder.go @@ -98,7 +98,7 @@ func (*cloneVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQ if err != nil { return err } - err = config.UpdateConfig(vmRef, client) + _, err = config.Update(false, vmRef, client) if err != nil { return err } diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index aa53e202..1cea5cec 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -102,6 +102,7 @@ type FlatConfig struct { 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"` @@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*proxmox.Flatrng0Config)(nil).HCL2Spec())}, + "tpm_config": &hcldec.BlockSpec{TypeName: "tpm_config", Nested: hcldec.ObjectSpec((*proxmox.FlattpmConfig)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())}, diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index 88a6ef79..50459603 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 +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,NICConfig,diskConfig,rng0Config,pciDeviceConfig,vgaConfig,additionalISOsConfig,efiConfig,tpmConfig package proxmox @@ -136,6 +136,8 @@ type Config struct { Machine string `mapstructure:"machine"` // Configure Random Number Generator via VirtIO. See [VirtIO RNG device](#virtio-rng-device) Rng0 rng0Config `mapstructure:"rng0"` + // Set the tpmstate storage options. See [TPM Config](#tpm-config). + TPMConfig tpmConfig `mapstructure:"tpm_config"` // The graphics adapter to use. See [VGA Config](#vga-config). VGA vgaConfig `mapstructure:"vga"` // The network adapter to use. See [Network Adapters](#network-adapters) @@ -371,6 +373,35 @@ type efiConfig struct { EFIType string `mapstructure:"efi_type"` } +// Set the tpmstate storage options. +// +// HCL2 example: +// +// ```hcl +// +// tpm_config { +// tpm_storage_pool = "local" +// tpm_version = "v1.2" +// } +// +// ``` +// Usage example (JSON): +// +// ```json +// +// "tpm_config": { +// "tpm_storage_pool": "local", +// "tpm_version": "v1.2" +// } +// +// ``` +type tpmConfig struct { + // Name of the Proxmox storage pool to store the EFI disk on. + TPMStoragePool string `mapstructure:"tpm_storage_pool"` + // Version of TPM spec. Can be `v1.2` or `v2.0` Defaults to `v2.0`. + Version string `mapstructure:"tpm_version"` +} + // - `rng0` (object): Configure Random Number Generator via VirtIO. // A virtual hardware-RNG can be used to provide entropy from the host system to a guest VM helping avoid entropy starvation which might cause the guest system slow down. // The device is sourced from a host device and guest, his use can be limited: `max_bytes` bytes of data will become available on a `period` ms timer. @@ -602,6 +633,10 @@ 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) @@ -635,6 +670,28 @@ 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))) @@ -791,6 +848,18 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st errs = packersdk.MultiErrorAppend(errs, errors.New("efi_storage_pool not set for efi_config")) } } + if c.TPMConfig != (tpmConfig{}) { + if c.TPMConfig.TPMStoragePool == "" { + errs = packersdk.MultiErrorAppend(errs, errors.New("tpm_storage_pool not set for tpm_config")) + } + if c.TPMConfig.Version == "" { + log.Printf("TPM state device defined, but no tpm_version given, using v2.0") + c.TPMConfig.Version = "v2.0" + } + if !(c.TPMConfig.Version == "v1.2" || c.TPMConfig.Version == "v2.0") { + errs = packersdk.MultiErrorAppend(errs, errors.New("TPM Version must be one of \"v1.2\", \"v2.0\"")) + } + } if c.Rng0 != (rng0Config{}) { if !(c.Rng0.Source == "/dev/urandom" || c.Rng0.Source == "/dev/random" || c.Rng0.Source == "/dev/hwrng") { errs = packersdk.MultiErrorAppend(errs, errors.New("source must be one of \"/dev/urandom\", \"/dev/random\", \"/dev/hwrng\"")) diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index e5cf36f9..b2078ce2 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -101,6 +101,7 @@ type FlatConfig struct { 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"` @@ -222,6 +223,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*Flatrng0Config)(nil).HCL2Spec())}, + "tpm_config": &hcldec.BlockSpec{TypeName: "tpm_config", Nested: hcldec.ObjectSpec((*FlattpmConfig)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())}, @@ -462,6 +464,31 @@ func (*Flatrng0Config) HCL2Spec() map[string]hcldec.Spec { return s } +// FlattpmConfig is an auto-generated flat version of tpmConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlattpmConfig struct { + TPMStoragePool *string `mapstructure:"tpm_storage_pool" cty:"tpm_storage_pool" hcl:"tpm_storage_pool"` + Version *string `mapstructure:"tpm_version" cty:"tpm_version" hcl:"tpm_version"` +} + +// FlatMapstructure returns a new FlattpmConfig. +// FlattpmConfig is an auto-generated flat version of tpmConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*tpmConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlattpmConfig) +} + +// HCL2Spec returns the hcl spec of a tpmConfig. +// This spec is used by HCL to read the fields of tpmConfig. +// The decoded values from this spec will then be applied to a FlattpmConfig. +func (*FlattpmConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "tpm_storage_pool": &hcldec.AttrSpec{Name: "tpm_storage_pool", Type: cty.String, Required: false}, + "tpm_version": &hcldec.AttrSpec{Name: "tpm_version", Type: cty.String, Required: false}, + } + return s +} + // FlatvgaConfig is an auto-generated flat version of vgaConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatvgaConfig struct { diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 5d43e31b..12b6ec94 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -330,6 +330,67 @@ func TestRng0(t *testing.T) { } } +func TestTpm(t *testing.T) { + TpmTest := []struct { + name string + tpm_config tpmConfig + expectFailure bool + }{ + { + name: "version 1.2, no error", + expectFailure: false, + tpm_config: tpmConfig{ + TPMStoragePool: "local", + Version: "v1.2", + }, + }, + { + name: "version 2.0, no error", + expectFailure: false, + tpm_config: tpmConfig{ + TPMStoragePool: "local", + Version: "v2.0", + }, + }, + { + name: "empty storage pool, error", + expectFailure: true, + tpm_config: tpmConfig{ + TPMStoragePool: "", + Version: "v1.2", + }, + }, + { + name: "invalid Version, error", + expectFailure: true, + tpm_config: tpmConfig{ + TPMStoragePool: "local", + Version: "v6.2", + }, + }, + } + + for _, tt := range TpmTest { + t.Run(tt.name, func(t *testing.T) { + cfg := mandatoryConfig(t) + cfg["tpm_config"] = &tt.tpm_config + + var c Config + _, _, err := c.Prepare(&c, cfg) + if err != nil { + if !tt.expectFailure { + t.Fatalf("unexpected failure to prepare config: %s", err) + } + t.Logf("got expected failure: %s", err) + } + + if err == nil && tt.expectFailure { + t.Errorf("expected failure, but prepare succeeded") + } + }) + } +} + func TestSerials(t *testing.T) { serialsTest := []struct { name string diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 4c9bd859..7c94eb0d 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "log" + "reflect" "strconv" "strings" @@ -126,9 +127,10 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist EFIDisk: generateProxmoxEfi(c.EFIConfig), Machine: c.Machine, RNGDrive: generateProxmoxRng0(c.Rng0), + TPM: generateProxmoxTpm(c.TPMConfig), QemuVga: generateProxmoxVga(c.VGA), QemuNetworks: generateProxmoxNetworkAdapters(c.NICs), - QemuDisks: generateProxmoxDisks(c.Disks), + Disks: generateProxmoxDisks(c.Disks), QemuPCIDevices: generateProxmoxPCIDeviceMap(c.PCIDevices), QemuSerials: generateProxmoxSerials(c.Serials), Scsihw: c.SCSIController, @@ -277,33 +279,128 @@ func generateProxmoxNetworkAdapters(nics []NICConfig) proxmox.QemuDevices { } return devs } -func generateProxmoxDisks(disks []diskConfig) proxmox.QemuDevices { - devs := make(proxmox.QemuDevices) - for idx := range disks { - devs[idx] = make(proxmox.QemuDevice) - setDeviceParamIfDefined(devs[idx], "type", disks[idx].Type) - setDeviceParamIfDefined(devs[idx], "size", disks[idx].Size) - setDeviceParamIfDefined(devs[idx], "storage", disks[idx].StoragePool) - setDeviceParamIfDefined(devs[idx], "cache", disks[idx].CacheMode) - setDeviceParamIfDefined(devs[idx], "format", disks[idx].DiskFormat) - - if (devs[idx]["type"] == "scsi" || devs[idx]["type"] == "virtio") && - disks[idx].IOThread { - devs[idx]["iothread"] = "true" - } - if disks[idx].Discard { - devs[idx]["discard"] = "on" - } else { - devs[idx]["discard"] = "ignore" +func generateProxmoxDisks(disks []diskConfig) *proxmox.QemuStorages { + ideDisks := proxmox.QemuIdeDisks{} + sataDisks := proxmox.QemuSataDisks{} + scsiDisks := proxmox.QemuScsiDisks{} + virtIODisks := proxmox.QemuVirtIODisks{} + + ideCount := 0 + sataCount := 0 + scsiCount := 0 + virtIOCount := 0 + + for idx := range disks { + tmpSize, _ := strconv.ParseInt(disks[idx].Size[:len(disks[idx].Size)-1], 10, 0) + size := proxmox.QemuDiskSize(0) + switch disks[idx].Size[len(disks[idx].Size)-1:] { + case "T": + size = proxmox.QemuDiskSize(tmpSize) * 1073741824 + case "G": + size = proxmox.QemuDiskSize(tmpSize) * 1048576 + case "M": + size = proxmox.QemuDiskSize(tmpSize) * 1024 + case "K": + size = proxmox.QemuDiskSize(tmpSize) } - // Add SSD flag only if true - if disks[idx].SSD { - devs[idx]["ssd"] = "1" + switch disks[idx].Type { + case "ide": + dev := proxmox.QemuIdeStorage{ + Disk: &proxmox.QemuIdeDisk{ + SizeInKibibytes: size, + Storage: disks[idx].StoragePool, + Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), + Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), + Discard: disks[idx].Discard, + 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++ + case "scsi": + dev := proxmox.QemuScsiStorage{ + Disk: &proxmox.QemuScsiDisk{ + SizeInKibibytes: size, + Storage: disks[idx].StoragePool, + Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), + Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), + Discard: disks[idx].Discard, + EmulateSSD: disks[idx].SSD, + IOThread: disks[idx].IOThread, + }, + } + reflect.ValueOf(&scsiDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", scsiCount)). + Set(reflect.ValueOf(&dev)) + scsiCount++ + case "sata": + dev := proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: size, + Storage: disks[idx].StoragePool, + Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), + Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), + Discard: disks[idx].Discard, + EmulateSSD: disks[idx].SSD, + }, + } + reflect.ValueOf(&sataDisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", sataCount)). + Set(reflect.ValueOf(&dev)) + sataCount++ + case "virtio": + dev := proxmox.QemuVirtIOStorage{ + Disk: &proxmox.QemuVirtIODisk{ + SizeInKibibytes: size, + Storage: disks[idx].StoragePool, + Cache: proxmox.QemuDiskCache(disks[idx].CacheMode), + Format: proxmox.QemuDiskFormat(disks[idx].DiskFormat), + Discard: disks[idx].Discard, + IOThread: disks[idx].IOThread, + }, + } + reflect.ValueOf(&virtIODisks).Elem(). + FieldByName(fmt.Sprintf("Disk_%d", virtIOCount)). + Set(reflect.ValueOf(&dev)) + virtIOCount++ } } - return devs + return &proxmox.QemuStorages{ + Ide: &ideDisks, + Sata: &sataDisks, + Scsi: &scsiDisks, + VirtIO: &virtIODisks, + } } func generateProxmoxPCIDeviceMap(devices []pciDeviceConfig) proxmox.QemuDevices { @@ -375,6 +472,19 @@ func generateProxmoxEfi(efi efiConfig) proxmox.QemuDevice { return dev } +func generateProxmoxTpm(tpm tpmConfig) *proxmox.TpmState { + // If no TPM config is presented, don't return a TpmState device + if tpm == (tpmConfig{}) { + return nil + } + + dev := proxmox.TpmState{ + Storage: tpm.TPMStoragePool, + Version: (*proxmox.TpmVersion)(&tpm.Version), + } + return &dev +} + func setDeviceParamIfDefined(dev proxmox.QemuDevice, key, value string) { // Empty string is considered as not defined if value != "" { diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index 6520213b..7ca87200 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/Telmate/proxmox-api-go/proxmox" - "github.com/google/go-cmp/cmp" "github.com/hashicorp/packer-plugin-sdk/common" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -504,7 +503,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { tests := []struct { name string disks []diskConfig - expectOutput proxmox.QemuDevices + expectOutput *proxmox.QemuStorages }{ { "plain config, no special option set", @@ -520,15 +519,23 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - proxmox.QemuDevices{ - 0: proxmox.QemuDevice{ - "type": "scsi", - "discard": "ignore", - "size": "10G", - "storage": "local-lvm", - "cache": "none", - "format": "qcow2", + &proxmox.QemuStorages{ + Ide: &proxmox.QemuIdeDisks{}, + Sata: &proxmox.QemuSataDisks{}, + Scsi: &proxmox.QemuScsiDisks{ + Disk_0: &proxmox.QemuScsiStorage{ + Disk: &proxmox.QemuScsiDisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + EmulateSSD: false, + IOThread: false, + }, + }, }, + VirtIO: &proxmox.QemuVirtIODisks{}, }, }, { @@ -545,16 +552,23 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - proxmox.QemuDevices{ - 0: proxmox.QemuDevice{ - "type": "scsi", - "discard": "ignore", - "size": "10G", - "storage": "local-lvm", - "cache": "none", - "format": "qcow2", - "iothread": "true", + &proxmox.QemuStorages{ + Ide: &proxmox.QemuIdeDisks{}, + Sata: &proxmox.QemuSataDisks{}, + Scsi: &proxmox.QemuScsiDisks{ + Disk_0: &proxmox.QemuScsiStorage{ + Disk: &proxmox.QemuScsiDisk{ + SizeInKibibytes: 10485760, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + EmulateSSD: false, + IOThread: true, + }, + }, }, + VirtIO: &proxmox.QemuVirtIODisks{}, }, }, { @@ -571,15 +585,176 @@ func TestGenerateProxmoxDisks(t *testing.T) { SSD: false, }, }, - proxmox.QemuDevices{ - 0: proxmox.QemuDevice{ - "type": "virtio", - "discard": "ignore", - "size": "10G", - "storage": "local-lvm", - "cache": "none", - "format": "qcow2", - "iothread": "true", + &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, + }, + }, + }, + }, + }, + { + "bunch of disks, should be defined in the discovery order", + []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: "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, + }, + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "15G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "scsi", + StoragePool: "local-lvm", + Size: "16G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + { + Type: "virtio", + StoragePool: "local-lvm", + Size: "17G", + CacheMode: "none", + DiskFormat: "qcow2", + IOThread: true, + }, + }, + &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, + }, + }, + }, + Sata: &proxmox.QemuSataDisks{ + Disk_0: &proxmox.QemuSataStorage{ + Disk: &proxmox.QemuSataDisk{ + SizeInKibibytes: 11534336, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + }, + }, + Disk_1: &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, + }, + }, + Disk_1: &proxmox.QemuScsiStorage{ + Disk: &proxmox.QemuScsiDisk{ + SizeInKibibytes: 16777216, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + }, + }, + }, + VirtIO: &proxmox.QemuVirtIODisks{ + Disk_0: &proxmox.QemuVirtIOStorage{ + Disk: &proxmox.QemuVirtIODisk{ + SizeInKibibytes: 15728640, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + }, + }, + Disk_1: &proxmox.QemuVirtIOStorage{ + Disk: &proxmox.QemuVirtIODisk{ + SizeInKibibytes: 17825792, + Storage: "local-lvm", + Cache: proxmox.QemuDiskCache("none"), + Format: proxmox.QemuDiskFormat("qcow2"), + Discard: false, + IOThread: true, + }, + }, }, }, }, @@ -588,10 +763,7 @@ func TestGenerateProxmoxDisks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devs := generateProxmoxDisks(tt.disks) - diff := cmp.Diff(devs, tt.expectOutput) - if diff != "" { - t.Errorf("mismatch in produced qemu disks specs: %s", diff) - } + assert.Equal(t, devs, tt.expectOutput) }) } } diff --git a/builder/proxmox/iso/builder.go b/builder/proxmox/iso/builder.go index ba4eb445..cafdb7e6 100644 --- a/builder/proxmox/iso/builder.go +++ b/builder/proxmox/iso/builder.go @@ -5,6 +5,7 @@ package proxmoxiso import ( "context" + "strings" proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" "github.com/hashicorp/hcl/v2/hcldec" @@ -70,9 +71,12 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) type isoVMCreator struct{} func (*isoVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error { - isoFile := state.Get("iso_file").(string) - config.QemuIso = isoFile + isoFile := strings.Split(state.Get("iso_file").(string), ":iso/") + config.Iso = &proxmoxapi.IsoFile{ + File: isoFile[1], + Storage: isoFile[0], + } client := state.Get("proxmoxClient").(*proxmoxapi.Client) - return config.CreateVm(vmRef, client) + return config.Create(vmRef, client) } diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index 7a275567..c934fe5f 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -102,6 +102,7 @@ type FlatConfig struct { 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"` @@ -232,6 +233,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*proxmox.Flatrng0Config)(nil).HCL2Spec())}, + "tpm_config": &hcldec.BlockSpec{TypeName: "tpm_config", Nested: hcldec.ObjectSpec((*proxmox.FlattpmConfig)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())}, diff --git a/docs-partials/builder/proxmox/common/Config-not-required.mdx b/docs-partials/builder/proxmox/common/Config-not-required.mdx index 643bb2ff..cbf6a896 100644 --- a/docs-partials/builder/proxmox/common/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/common/Config-not-required.mdx @@ -84,6 +84,8 @@ - `rng0` (rng0Config) - Configure Random Number Generator via VirtIO. See [VirtIO RNG device](#virtio-rng-device) +- `tpm_config` (tpmConfig) - Set the tpmstate storage options. See [TPM Config](#tpm-config). + - `vga` (vgaConfig) - The graphics adapter to use. See [VGA Config](#vga-config). - `network_adapters` ([]NICConfig) - The network adapter to use. See [Network Adapters](#network-adapters) diff --git a/docs-partials/builder/proxmox/common/tpmConfig-not-required.mdx b/docs-partials/builder/proxmox/common/tpmConfig-not-required.mdx new file mode 100644 index 00000000..c340928e --- /dev/null +++ b/docs-partials/builder/proxmox/common/tpmConfig-not-required.mdx @@ -0,0 +1,7 @@ + + +- `tpm_storage_pool` (string) - Name of the Proxmox storage pool to store the EFI disk on. + +- `tpm_version` (string) - Version of TPM spec. Can be `v1.2` or `v2.0` Defaults to `v2.0`. + + diff --git a/docs-partials/builder/proxmox/common/tpmConfig.mdx b/docs-partials/builder/proxmox/common/tpmConfig.mdx new file mode 100644 index 00000000..0ac988d5 --- /dev/null +++ b/docs-partials/builder/proxmox/common/tpmConfig.mdx @@ -0,0 +1,26 @@ + + +Set the tpmstate storage options. + +HCL2 example: + +```hcl + + tpm_config { + tpm_storage_pool = "local" + tpm_version = "v1.2" + } + +``` +Usage example (JSON): + +```json + + "tpm_config": { + "tpm_storage_pool": "local", + "tpm_version": "v1.2" + } + +``` + + diff --git a/go.mod b/go.mod index 592bd942..6e0c2259 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/packer-plugin-proxmox go 1.19 require ( - github.com/Telmate/proxmox-api-go v0.0.0-20230524203107-41e6ffadedb1 + github.com/Telmate/proxmox-api-go v0.0.0-20240409105641-32c480fe008e github.com/hashicorp/go-getter/v2 v2.2.1 github.com/hashicorp/hcl/v2 v2.19.1 github.com/hashicorp/packer-plugin-sdk v0.5.2 diff --git a/go.sum b/go.sum index c733384e..22995d9e 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,10 @@ github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nu github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Telmate/proxmox-api-go v0.0.0-20230524203107-41e6ffadedb1 h1:A1psGmAosA1Upwq03FN14SLRbJymKE0+QTTVSsFsEuo= github.com/Telmate/proxmox-api-go v0.0.0-20230524203107-41e6ffadedb1/go.mod h1:HKwnwBcgJxT+UjTUyRP7+aDxXSgu0kLWvlrRhd4i1YU= +github.com/Telmate/proxmox-api-go v0.0.0-20240409105641-32c480fe008e h1:NdpVflh7VclkvfKkIzMngbEfDdwFcesJpyMkWldZoIs= +github.com/Telmate/proxmox-api-go v0.0.0-20240409105641-32c480fe008e/go.mod h1:bscBzOUx0tJAdVGmQvcnoWPg5eI2eJ6anJKV1ueZ1oU= +github.com/Telmate/proxmox-api-go v0.0.0-20240409105808-2f0ce9e71d65 h1:nfgs+oHm0f/cTjkE5wF9aRrlxBKRPFeNpokKnBPr49M= +github.com/Telmate/proxmox-api-go v0.0.0-20240409105808-2f0ce9e71d65/go.mod h1:bscBzOUx0tJAdVGmQvcnoWPg5eI2eJ6anJKV1ueZ1oU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=