Repository: Telmate/terraform-provider-proxmox
Pull request #1263: Feat tpmstate efi secureboot
Author: login: galthaus
Labels: name: modifies/go
description: Pull requests that update Go code
name: size/M
description: Denotes a PR that changes 30-99 lines, ignoring generated files
name: type/feature
description: Completely new functionality
name: resource/qemu
description: Issue or PR related to Qemu resource
name: test/done
description: This PR has been tested and the result was succesfull
Created: 2025-03-11T14:41:23Z | Merged: 2025-03-16T20:24:07Z

This MR adds features in two commits.

1. pre_enrolled_keys to the efidisk stanza.  This gets translated to the pre-enrolled-keys flag to turn on secure boot.
2. Add tpmstate stanza to configure the tpm in the VM.  This takes a storage location and a version.

These were tested against v3.0.1-rc6.  The master tree has additional issues.

 The code difference between the two points on the tree is the pxapi rename to pveSDK.


Diff:
diff --git a/proxmox/Internal/resource/guest/qemu/tpm/schema.go b/proxmox/Internal/resource/guest/qemu/tpm/schema.go
new file mode 100644
index 00000000..a5770350
--- /dev/null
+++ b/proxmox/Internal/resource/guest/qemu/tpm/schema.go
@@ -0,0 +1,42 @@
+package tpm
+
+import (
+	pveAPI "github.com/Telmate/proxmox-api-go/proxmox"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+const (
+	Root       = "tpm_state"
+	storageKey = "storage"
+	versionKey = "version"
+
+	versionValueTpmV12 = string(pveAPI.TpmVersion_1_2) // v1.2
+	versionValueTpmV20 = string(pveAPI.TpmVersion_2_0) // v2.0
+)
+
+func Schema() *schema.Schema {
+	return &schema.Schema{
+		Type:     schema.TypeList,
+		Optional: true,
+		MaxItems: 1,
+		Elem: &schema.Resource{
+			Schema: map[string]*schema.Schema{
+				storageKey: {
+					Type:     schema.TypeString,
+					Required: true,
+					ForceNew: true,
+				},
+				versionKey: {
+					Type:     schema.TypeString,
+					Optional: true,
+					Default:  versionValueTpmV20,
+					ValidateFunc: validation.StringInSlice([]string{
+						versionValueTpmV20,
+						versionValueTpmV12,
+					}, false),
+					ForceNew: true,
+				},
+			},
+		}}
+}
diff --git a/proxmox/Internal/resource/guest/qemu/tpm/sdk.go b/proxmox/Internal/resource/guest/qemu/tpm/sdk.go
new file mode 100644
index 00000000..ba430a82
--- /dev/null
+++ b/proxmox/Internal/resource/guest/qemu/tpm/sdk.go
@@ -0,0 +1,21 @@
+package tpm
+
+import (
+	pveAPI "github.com/Telmate/proxmox-api-go/proxmox"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func SDK(d *schema.ResourceData) *pveAPI.TpmState {
+	tmpList := d.Get("tpm_state").([]interface{})
+	if len(tmpList) == 0 {
+		return nil
+	}
+	tpmState := &pveAPI.TpmState{}
+	thisTpmMap := tmpList[0].(map[string]interface{})
+	tpmState.Storage = thisTpmMap[storageKey].(string)
+	if v, ok := thisTpmMap[versionKey].(string); ok {
+		tv := pveAPI.TpmVersion(v)
+		tpmState.Version = &tv
+	}
+	return tpmState
+}
diff --git a/proxmox/Internal/resource/guest/qemu/tpm/terraform.go b/proxmox/Internal/resource/guest/qemu/tpm/terraform.go
new file mode 100644
index 00000000..5c647933
--- /dev/null
+++ b/proxmox/Internal/resource/guest/qemu/tpm/terraform.go
@@ -0,0 +1,19 @@
+package tpm
+
+import (
+	pveAPI "github.com/Telmate/proxmox-api-go/proxmox"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func Terraform(config *pveAPI.TpmState, d *schema.ResourceData) {
+	if config == nil {
+		d.Set(Root, nil)
+		return
+	}
+	answer := map[string]interface{}{}
+	answer[storageKey] = config.Storage
+	answer[versionKey] = config.Version
+	tpms := make([]interface{}, 1)
+	tpms[0] = answer
+	d.Set(Root, tpms)
+}
diff --git a/proxmox/resource_vm_qemu.go b/proxmox/resource_vm_qemu.go
index 1a942ee0..46fc70ea 100755
--- a/proxmox/resource_vm_qemu.go
+++ b/proxmox/resource_vm_qemu.go
@@ -34,6 +34,7 @@ import (
 	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/qemu/network"
 	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/qemu/pci"
 	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/qemu/serial"
+	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/qemu/tpm"
 	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/qemu/usb"
 	"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/sshkeys"
 	vmID "github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/resource/guest/vmid"
@@ -384,6 +385,7 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			tpm.Root:          tpm.Schema(),
 			"efidisk": {
 				Type:     schema.TypeList,
 				Optional: true,
@@ -394,6 +396,12 @@ func resourceVmQemu() *schema.Resource {
 							Required: true,
 							ForceNew: true,
 						},
+						"pre_enrolled_keys": {
+							Type:     schema.TypeBool,
+							Optional: true,
+							Default:  false,
+							ForceNew: true,
+						},
 						"efitype": {
 							Type:     schema.TypeString,
 							Optional: true,
@@ -703,6 +711,7 @@ func resourceVmQemuCreate(ctx context.Context, d *schema.ResourceData, meta inte
 		Serials:     serial.SDK(d),
 		Smbios1:     BuildSmbiosArgs(d.Get("smbios").([]interface{})),
 		CloudInit:   mapToSDK_CloudInit(d),
+		TPM:         tpm.SDK(d),
 	}
 
 	var diags, tmpDiags diag.Diagnostics
@@ -958,6 +967,7 @@ func resourceVmQemuUpdate(ctx context.Context, d *schema.ResourceData, meta inte
 		Serials:     serial.SDK(d),
 		Smbios1:     BuildSmbiosArgs(d.Get("smbios").([]interface{})),
 		CloudInit:   mapToSDK_CloudInit(d),
+		TPM:         tpm.SDK(d),
 	}
 	if len(qemuVgaList) > 0 {
 		config.QemuVga = qemuVgaList[0].(map[string]interface{})
@@ -1385,7 +1395,7 @@ func DropElementsFromMap(elements []string, mapList []map[string]interface{}) ([
 	return mapList, nil
 }
 
-// Consumes an API return (pxapi.QemuDevices) and "flattens" it into a []map[string]interface{} as
+// Consumes an API return (pveSDK.QemuDevices) and "flattens" it into a []map[string]interface{} as
 // expected by the terraform interface for TypeList
 func FlattenDevicesList(proxmoxDevices pveSDK.QemuDevices) ([]map[string]interface{}, error) {
 	flattenedDevices := make([]map[string]interface{}, 0, 1)
@@ -1483,7 +1493,7 @@ func ReadSmbiosArgs(smbios string) []interface{} {
 }
 
 // Consumes a terraform TypeList of a Qemu Device (network, hard drive, etc) and returns the "Expanded"
-// version of the equivalent configuration that the API understands (the struct pxapi.QemuDevices).
+// version of the equivalent configuration that the API understands (the struct pveSDK.QemuDevices).
 // NOTE this expects the provided deviceList to be []map[string]interface{}.
 func ExpandDevicesList(deviceList []interface{}) (pveSDK.QemuDevices, error) {
 	expandedDevices := make(pveSDK.QemuDevices)
@@ -1506,6 +1516,10 @@ func ExpandDevicesList(deviceList []interface{}) (pveSDK.QemuDevices, error) {
 		// this is a map of string->interface, loop over it and move it into
 		// the qemuDevices struct
 		for configuration, value := range thisDeviceMap {
+			// XXX: Not sure where to put these
+			if configuration == "pre_enrolled_keys" {
+				configuration = "pre-enrolled-keys"
+			}
 			thisExpandedDevice[configuration] = value
 		}
 
@@ -1745,7 +1759,7 @@ func mapToTerraform_Description(description *string) string {
 }
 
 func mapToTerraform_Memory(config *pveSDK.QemuMemory, d *schema.ResourceData) {
-	// no nil check as pxapi.QemuMemory is always returned
+	// no nil check as pveSDK.QemuMemory is always returned
 	if config.CapacityMiB != nil {
 		d.Set("memory", int(*config.CapacityMiB))
 	}


Reviews:
user: login: Tinyblargon
body: Thank you so much for your contribution.

Sorry to ask you to move the code to a different place, but this file in particular kept becoming difficult to maintain due to the amount of code in it.

Further there are 2 minor schema changes.


state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T23:08:16Z
user: login: galthaus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T01:39:46Z
user: login: galthaus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T01:39:58Z
user: login: galthaus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T02:06:58Z
user: login: galthaus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T02:31:43Z

Review comments:
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
path: proxmox/resource_vm_qemu.go
user: login: Tinyblargon
body: Could you rename it to `tpm_state`?
created_at: 2025-03-12T22:48:21Z
author_association: COLLABORATOR
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
+				Type:     schema.TypeList,
path: proxmox/resource_vm_qemu.go
user: login: Tinyblargon
body: `MaxItems` should be set to `1`
Would probably eliminate length check at 1523
created_at: 2025-03-12T22:53:56Z
author_association: COLLABORATOR
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
path: proxmox/resource_vm_qemu.go
user: login: Tinyblargon
body: Could you create a `tpm` folder in https://github.com/Telmate/terraform-provider-proxmox/tree/master/proxmox/Internal/resource/guest/qemu and move this code (L387-L409) to that package. The serial package explains the structure https://github.com/Telmate/terraform-provider-proxmox/tree/master/proxmox/Internal/resource/guest/qemu/serial
created_at: 2025-03-12T23:00:18Z
author_association: COLLABORATOR
diff_hunk: @@ -1482,8 +1514,26 @@ func ReadSmbiosArgs(smbios string) []interface{} {
 	return append(smbiosArgs, smbiosMap)
 }
 
+func ExpandTPMList(tmpList []interface{}) (*pveSDK.TpmState, error) {
path: proxmox/resource_vm_qemu.go
user: login: Tinyblargon
body: Could you create a `tpm` folder in https://github.com/Telmate/terraform-provider-proxmox/tree/master/proxmox/Internal/resource/guest/qemu and move this code (L1517-L1534) to that package. The serial package explains the structure https://github.com/Telmate/terraform-provider-proxmox/tree/master/proxmox/Internal/resource/guest/qemu/serial
created_at: 2025-03-12T23:02:05Z
author_association: COLLABORATOR
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
path: proxmox/resource_vm_qemu.go
user: login: galthaus
body: Will do
created_at: 2025-03-13T01:39:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
+				Type:     schema.TypeList,
path: proxmox/resource_vm_qemu.go
user: login: galthaus
body: Will do and removed length check.
created_at: 2025-03-13T01:39:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -384,6 +384,29 @@ func resourceVmQemu() *schema.Resource {
 			pci.RootLegacyPCI: pci.SchemaLegacyPCI(),
 			pci.RootPCI:       pci.SchemaPCI(),
 			pci.RootPCIs:      pci.SchemaPCIs(),
+			"tpmstate": {
path: proxmox/resource_vm_qemu.go
user: login: galthaus
body: Will do.  cleaner.
created_at: 2025-03-13T02:06:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1482,8 +1514,26 @@ func ReadSmbiosArgs(smbios string) []interface{} {
 	return append(smbiosArgs, smbiosMap)
 }
 
+func ExpandTPMList(tmpList []interface{}) (*pveSDK.TpmState, error) {
path: proxmox/resource_vm_qemu.go
user: login: galthaus
body: done
created_at: 2025-03-13T02:31:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: sygibson
created_at: 2025-03-11T18:16:27Z
body: @galthaus - thank you for putting this together - we really need this in our use case - looking forward to this getting pulled in !!
author_association: CONTRIBUTOR
user: login: galthaus
created_at: 2025-03-13T02:39:49Z
body: Updated and tested based upon 301-rc6.

Testing against master breaks with all sorts of errors.

For example: the ParseMAC calls will fail because ParseMAC only parses lowercase mac address.  All calls should be called with strings.ToLower.  This causes net processing to fail on update because proxmox returns up-cased macs.

For example:  the SDK library crashes at line 650 in config_qemu.go because the vmr structure is filled in after it is used.  Flopping these stanzas resolve this issue.

There is another plugin crash that I hadn't tracked down.  I'm quite certain they aren't related to this MR/PR.

It seems like master is not in a usable state for qemu resources.


author_association: CONTRIBUTOR
user: login: Tinyblargon
created_at: 2025-03-16T20:23:58Z
body: @galthaus did some testing it seems all right. Thank you for this contribution.
author_association: COLLABORATOR