mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
feat(vm): Add support for setting the VM TPM State device (#743)
* feat(vm): add support for setting the vm tpm state Signed-off-by: Rui Lopes <rgl@ruilopes.com> * feat(vm): add encoding / unmarshalling for CustomTPMState Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * feat(vm): fix typos Signed-off-by: Rui Lopes <rgl@ruilopes.com> * feat(vm): fix vmGetTPMState Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * feat(docs): add the vm tpm_state documentation Signed-off-by: Rui Lopes <rgl@ruilopes.com> * feat(docs): add the vm tpm_state example Signed-off-by: Rui Lopes <rgl@ruilopes.com> --------- Signed-off-by: Rui Lopes <rgl@ruilopes.com> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
d1f2093d39
commit
66bba2a027
@ -65,6 +65,10 @@ resource "proxmox_virtual_environment_vm" "ubuntu_vm" {
|
||||
type = "l26"
|
||||
}
|
||||
|
||||
tpm_state {
|
||||
version = "v2.0"
|
||||
}
|
||||
|
||||
serial_device {}
|
||||
}
|
||||
|
||||
@ -287,6 +291,11 @@ output "ubuntu_vm_public_key" {
|
||||
distribution-specific and Microsoft Standard keys enrolled, if used with
|
||||
EFI type=`4m`. Ignored for VMs with cpu.architecture=`aarch64` (defaults
|
||||
to `false`).
|
||||
- `tpm_state` - (Optional) The TPM state device.
|
||||
- `datastore_id` (Optional) The identifier for the datastore to create
|
||||
the disk in (defaults to `local-lvm`).
|
||||
- `version` (Optional) TPM state device version. Can be `v1.2` or `v2.0`.
|
||||
(defaults to `v2.0`).
|
||||
- `hostpci` - (Optional) A host PCI device mapping (multiple blocks supported).
|
||||
- `device` - (Required) The PCI device name for Proxmox, in form
|
||||
of `hostpciX` where `X` is a sequential number from 0 to 3.
|
||||
|
@ -33,6 +33,11 @@ resource "proxmox_virtual_environment_vm" "example_template" {
|
||||
type = "4m"
|
||||
}
|
||||
|
||||
tpm_state {
|
||||
datastore_id = local.datastore_id
|
||||
version = "v2.0"
|
||||
}
|
||||
|
||||
# disk {
|
||||
# datastore_id = local.datastore_id
|
||||
# file_id = proxmox_virtual_environment_file.ubuntu_cloud_image.id
|
||||
|
@ -239,6 +239,12 @@ func (r CustomStorageDevice) IsOwnedBy(vmID int) bool {
|
||||
// CustomStorageDevices handles QEMU SATA device parameters.
|
||||
type CustomStorageDevices map[string]CustomStorageDevice
|
||||
|
||||
// CustomTPMState handles QEMU TPM state parameters.
|
||||
type CustomTPMState struct {
|
||||
FileVolume string `json:"file" url:"file"`
|
||||
Version *string `json:"version,omitempty" url:"version,omitempty"`
|
||||
}
|
||||
|
||||
// CustomUSBDevice handles QEMU USB device parameters.
|
||||
type CustomUSBDevice struct {
|
||||
HostDevice *string `json:"host" url:"host"`
|
||||
@ -349,6 +355,7 @@ type CreateRequestBody struct {
|
||||
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
|
||||
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
|
||||
TimeDriftFixEnabled *types.CustomBool `json:"tdf,omitempty" url:"tdf,omitempty,int"`
|
||||
TPMState *CustomTPMState `json:"tpmstate0,omitempty" url:"tpmstate0,omitempty"`
|
||||
USBDevices CustomUSBDevices `json:"usb,omitempty" url:"usb,omitempty"`
|
||||
VGADevice *CustomVGADevice `json:"vga,omitempty" url:"vga,omitempty"`
|
||||
VirtualCPUCount *int `json:"vcpus,omitempty" url:"vcpus,omitempty"`
|
||||
@ -517,6 +524,7 @@ type GetResponseData struct {
|
||||
Tags *string `json:"tags,omitempty"`
|
||||
Template *types.CustomBool `json:"template,omitempty"`
|
||||
TimeDriftFixEnabled *types.CustomBool `json:"tdf,omitempty"`
|
||||
TPMState *CustomTPMState `json:"tpmstate0,omitempty"`
|
||||
USBDevice0 *CustomUSBDevice `json:"usb0,omitempty"`
|
||||
USBDevice1 *CustomUSBDevice `json:"usb1,omitempty"`
|
||||
USBDevice2 *CustomUSBDevice `json:"usb2,omitempty"`
|
||||
@ -1233,6 +1241,21 @@ func (r CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomTPMState struct to a URL vlaue.
|
||||
func (r CustomTPMState) EncodeValues(key string, v *url.Values) error {
|
||||
values := []string{
|
||||
fmt.Sprintf("file=%s", r.FileVolume),
|
||||
}
|
||||
|
||||
if r.Version != nil {
|
||||
values = append(values, fmt.Sprintf("version=%s", *r.Version))
|
||||
}
|
||||
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomUSBDevice struct to a URL vlaue.
|
||||
func (r CustomUSBDevice) EncodeValues(key string, v *url.Values) error {
|
||||
if r.HostDevice == nil && r.Mapping == nil {
|
||||
@ -1707,6 +1730,33 @@ func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomTPMState string to an object.
|
||||
func (r *CustomTPMState) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomTPMState: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
if len(v) == 1 {
|
||||
r.FileVolume = v[0]
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "file":
|
||||
r.FileVolume = v[1]
|
||||
case "version":
|
||||
r.Version = &v[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomUSBDevice string to an object.
|
||||
func (r *CustomUSBDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
@ -78,6 +78,8 @@ const (
|
||||
dvResourceVirtualEnvironmentVMEFIDiskFileFormat = "qcow2"
|
||||
dvResourceVirtualEnvironmentVMEFIDiskType = "2m"
|
||||
dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = false
|
||||
dvResourceVirtualEnvironmentVMTPMStateDatastoreID = "local-lvm"
|
||||
dvResourceVirtualEnvironmentVMTPMStateVersion = "v2.0"
|
||||
dvResourceVirtualEnvironmentVMInitializationDatastoreID = "local-lvm"
|
||||
dvResourceVirtualEnvironmentVMInitializationInterface = ""
|
||||
dvResourceVirtualEnvironmentVMInitializationDNSDomain = ""
|
||||
@ -199,6 +201,9 @@ const (
|
||||
mkResourceVirtualEnvironmentVMEFIDiskFileFormat = "file_format"
|
||||
mkResourceVirtualEnvironmentVMEFIDiskType = "type"
|
||||
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = "pre_enrolled_keys"
|
||||
mkResourceVirtualEnvironmentVMTPMState = "tpm_state"
|
||||
mkResourceVirtualEnvironmentVMTPMStateDatastoreID = "datastore_id"
|
||||
mkResourceVirtualEnvironmentVMTPMStateVersion = "version"
|
||||
mkResourceVirtualEnvironmentVMHostPCI = "hostpci"
|
||||
mkResourceVirtualEnvironmentVMHostPCIDevice = "device"
|
||||
mkResourceVirtualEnvironmentVMHostPCIDeviceID = "id"
|
||||
@ -794,6 +799,38 @@ func VM() *schema.Resource {
|
||||
MaxItems: 1,
|
||||
MinItems: 0,
|
||||
},
|
||||
mkResourceVirtualEnvironmentVMTPMState: {
|
||||
Type: schema.TypeList,
|
||||
Description: "The tpmstate device",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
DefaultFunc: func() (interface{}, error) {
|
||||
return []interface{}{}, nil
|
||||
},
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
mkResourceVirtualEnvironmentVMTPMStateDatastoreID: {
|
||||
Type: schema.TypeString,
|
||||
Description: "Datastore ID",
|
||||
Optional: true,
|
||||
Default: dvResourceVirtualEnvironmentVMTPMStateDatastoreID,
|
||||
},
|
||||
mkResourceVirtualEnvironmentVMTPMStateVersion: {
|
||||
Type: schema.TypeString,
|
||||
Description: "TPM version",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: dvResourceVirtualEnvironmentVMTPMStateVersion,
|
||||
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
|
||||
"v1.2",
|
||||
"v2.0",
|
||||
}, true)),
|
||||
},
|
||||
},
|
||||
},
|
||||
MaxItems: 1,
|
||||
MinItems: 0,
|
||||
},
|
||||
mkResourceVirtualEnvironmentVMInitialization: {
|
||||
Type: schema.TypeList,
|
||||
Description: "The cloud-init configuration",
|
||||
@ -2318,6 +2355,59 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
||||
}
|
||||
}
|
||||
|
||||
tpmState := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
||||
tpmStateInfo := vmGetTPMState(d, nil) // from the resource config
|
||||
|
||||
for i := range tpmState {
|
||||
diskBlock := tpmState[i].(map[string]interface{})
|
||||
diskInterface := "tpmstate0"
|
||||
dataStoreID := diskBlock[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
||||
|
||||
currentTPMState := vmConfig.TPMState
|
||||
configuredTPMStateInfo := tpmStateInfo
|
||||
|
||||
if currentTPMState == nil {
|
||||
diskUpdateBody := &vms.UpdateRequestBody{}
|
||||
|
||||
diskUpdateBody.TPMState = configuredTPMStateInfo
|
||||
|
||||
e = vmAPI.UpdateVM(ctx, diskUpdateBody)
|
||||
if e != nil {
|
||||
return diag.FromErr(e)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
deleteOriginalDisk := types.CustomBool(true)
|
||||
|
||||
diskMoveBody := &vms.MoveDiskRequestBody{
|
||||
DeleteOriginalDisk: &deleteOriginalDisk,
|
||||
Disk: diskInterface,
|
||||
TargetStorage: dataStoreID,
|
||||
}
|
||||
|
||||
moveDisk := false
|
||||
|
||||
if dataStoreID != "" {
|
||||
moveDisk = true
|
||||
|
||||
if allDiskInfo[diskInterface] != nil {
|
||||
fileIDParts := strings.Split(allDiskInfo[diskInterface].FileVolume, ":")
|
||||
moveDisk = dataStoreID != fileIDParts[0]
|
||||
}
|
||||
}
|
||||
|
||||
if moveDisk {
|
||||
moveDiskTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMoveDisk).(int)
|
||||
|
||||
e = vmAPI.MoveVMDisk(ctx, diskMoveBody, moveDiskTimeout)
|
||||
if e != nil {
|
||||
return diag.FromErr(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vmCreateStart(ctx, d, m)
|
||||
}
|
||||
|
||||
@ -2427,6 +2517,25 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
||||
}
|
||||
}
|
||||
|
||||
var tpmState *vms.CustomTPMState
|
||||
|
||||
tpmStateBlock := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
||||
if len(tpmStateBlock) > 0 {
|
||||
block := tpmStateBlock[0].(map[string]interface{})
|
||||
|
||||
datastoreID, _ := block[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
||||
version, _ := block[mkResourceVirtualEnvironmentVMTPMStateVersion].(string)
|
||||
|
||||
if version == "" {
|
||||
version = dvResourceVirtualEnvironmentVMTPMStateVersion
|
||||
}
|
||||
|
||||
tpmState = &vms.CustomTPMState{
|
||||
FileVolume: fmt.Sprintf("%s:1", datastoreID),
|
||||
Version: &version,
|
||||
}
|
||||
}
|
||||
|
||||
virtioDeviceObjects := diskDeviceObjects["virtio"]
|
||||
scsiDeviceObjects := diskDeviceObjects["scsi"]
|
||||
// ideDeviceObjects := getOrderedDiskDeviceList(diskDeviceObjects, "ide")
|
||||
@ -2606,6 +2715,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
||||
CPUUnits: &cpuUnits,
|
||||
DedicatedMemory: &memoryDedicated,
|
||||
EFIDisk: efiDisk,
|
||||
TPMState: tpmState,
|
||||
FloatingMemory: &memoryFloating,
|
||||
IDEDevices: ideDevices,
|
||||
KeyboardLayout: &keyboardLayout,
|
||||
@ -3215,7 +3325,8 @@ func vmGetEfiDisk(d *schema.ResourceData, disk []interface{}) *vms.CustomEFIDisk
|
||||
efiType, _ := block[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
|
||||
preEnrolledKeys := types.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
|
||||
|
||||
// special case for efi disk, the size is ignored, see docs for more info
|
||||
// use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.
|
||||
// NB SIZE_IN_GiB is ignored, see docs for more info.
|
||||
efiDiskConfig.FileVolume = fmt.Sprintf("%s:1", datastoreID)
|
||||
efiDiskConfig.Format = &fileFormat
|
||||
efiDiskConfig.Type = &efiType
|
||||
@ -3256,6 +3367,54 @@ func vmGetEfiDiskAsStorageDevice(d *schema.ResourceData, disk []interface{}) (*v
|
||||
return storageDevice, nil
|
||||
}
|
||||
|
||||
func vmGetTPMState(d *schema.ResourceData, disk []interface{}) *vms.CustomTPMState {
|
||||
var tpmState []interface{}
|
||||
|
||||
if disk != nil {
|
||||
tpmState = disk
|
||||
} else {
|
||||
tpmState = d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
||||
}
|
||||
|
||||
var tpmStateConfig *vms.CustomTPMState
|
||||
|
||||
if len(tpmState) > 0 {
|
||||
tpmStateConfig = &vms.CustomTPMState{}
|
||||
|
||||
block := tpmState[0].(map[string]interface{})
|
||||
datastoreID, _ := block[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
||||
version, _ := block[mkResourceVirtualEnvironmentVMTPMStateVersion].(string)
|
||||
|
||||
// use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.
|
||||
// NB SIZE_IN_GiB is ignored, see docs for more info.
|
||||
tpmStateConfig.FileVolume = fmt.Sprintf("%s:1", datastoreID)
|
||||
tpmStateConfig.Version = &version
|
||||
}
|
||||
|
||||
return tpmStateConfig
|
||||
}
|
||||
|
||||
func vmGetTPMStateAsStorageDevice(d *schema.ResourceData, disk []interface{}) *vms.CustomStorageDevice {
|
||||
tpmState := vmGetTPMState(d, disk)
|
||||
|
||||
var storageDevice *vms.CustomStorageDevice
|
||||
|
||||
if tpmState != nil {
|
||||
id := "0"
|
||||
baseDiskInterface := "tpmstate"
|
||||
diskInterface := fmt.Sprint(baseDiskInterface, id)
|
||||
|
||||
storageDevice = &vms.CustomStorageDevice{
|
||||
Enabled: true,
|
||||
FileVolume: tpmState.FileVolume,
|
||||
Interface: &diskInterface,
|
||||
ID: &id,
|
||||
}
|
||||
}
|
||||
|
||||
return storageDevice
|
||||
}
|
||||
|
||||
func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
|
||||
pciDevice := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
|
||||
pciDeviceObjects := make(vms.CustomPCIDevices, len(pciDevice))
|
||||
@ -4101,6 +4260,29 @@ func vmReadCustom(
|
||||
}
|
||||
}
|
||||
|
||||
if vmConfig.TPMState != nil {
|
||||
tpmState := map[string]interface{}{}
|
||||
|
||||
fileIDParts := strings.Split(vmConfig.TPMState.FileVolume, ":")
|
||||
|
||||
tpmState[mkResourceVirtualEnvironmentVMTPMStateDatastoreID] = fileIDParts[0]
|
||||
tpmState[mkResourceVirtualEnvironmentVMTPMStateVersion] = dvResourceVirtualEnvironmentVMTPMStateVersion
|
||||
|
||||
currentTPMState := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
||||
|
||||
if len(clone) > 0 {
|
||||
if len(currentTPMState) > 0 {
|
||||
err := d.Set(mkResourceVirtualEnvironmentVMTPMState, []interface{}{tpmState})
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
}
|
||||
} else if len(currentTPMState) > 0 ||
|
||||
tpmState[mkResourceVirtualEnvironmentVMTPMStateDatastoreID] != dvResourceVirtualEnvironmentVMTPMStateDatastoreID ||
|
||||
tpmState[mkResourceVirtualEnvironmentVMTPMStateVersion] != dvResourceVirtualEnvironmentVMTPMStateVersion {
|
||||
err := d.Set(mkResourceVirtualEnvironmentVMTPMState, []interface{}{tpmState})
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
}
|
||||
}
|
||||
|
||||
currentPCIList := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
|
||||
pciMap := map[string]interface{}{}
|
||||
|
||||
@ -5463,6 +5645,15 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
||||
rebootRequired = true
|
||||
}
|
||||
|
||||
// Prepare the new tpm state configuration.
|
||||
if d.HasChange(mkResourceVirtualEnvironmentVMTPMState) {
|
||||
tpmState := vmGetTPMState(d, nil)
|
||||
|
||||
updateBody.TPMState = tpmState
|
||||
|
||||
rebootRequired = true
|
||||
}
|
||||
|
||||
// Prepare the new cloud-init configuration.
|
||||
stoppedBeforeUpdate := false
|
||||
if d.HasChange(mkResourceVirtualEnvironmentVMInitialization) {
|
||||
@ -5777,6 +5968,30 @@ func vmUpdateDiskLocationAndSize(
|
||||
}
|
||||
}
|
||||
|
||||
// Add tpm state if it has changes
|
||||
if d.HasChange(mkResourceVirtualEnvironmentVMTPMState) {
|
||||
diskOld, diskNew := d.GetChange(mkResourceVirtualEnvironmentVMTPMState)
|
||||
|
||||
oldTPMState := vmGetTPMStateAsStorageDevice(d, diskOld.([]interface{}))
|
||||
newTPMState := vmGetTPMStateAsStorageDevice(d, diskNew.([]interface{}))
|
||||
|
||||
if oldTPMState != nil {
|
||||
baseDiskInterface := diskDigitPrefix(*oldTPMState.Interface)
|
||||
diskOldEntries[baseDiskInterface][*oldTPMState.Interface] = *oldTPMState
|
||||
}
|
||||
|
||||
if newTPMState != nil {
|
||||
baseDiskInterface := diskDigitPrefix(*newTPMState.Interface)
|
||||
diskNewEntries[baseDiskInterface][*newTPMState.Interface] = *newTPMState
|
||||
}
|
||||
|
||||
if oldTPMState != nil && newTPMState != nil && oldTPMState.Size != newTPMState.Size {
|
||||
return diag.Errorf(
|
||||
"resizing of tpm state is not supported.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var diskMoveBodies []*vms.MoveDiskRequestBody
|
||||
|
||||
var diskResizeBodies []*vms.ResizeDiskRequestBody
|
||||
@ -6052,6 +6267,11 @@ func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string
|
||||
datastoresSet[fileIDParts[0]] = 1
|
||||
}
|
||||
|
||||
if vm.TPMState != nil {
|
||||
fileIDParts := strings.Split(vm.TPMState.FileVolume, ":")
|
||||
datastoresSet[fileIDParts[0]] = 1
|
||||
}
|
||||
|
||||
datastores := []string{}
|
||||
for datastore := range datastoresSet {
|
||||
datastores = append(datastores, datastore)
|
||||
|
Loading…
Reference in New Issue
Block a user