From 80cafa689f3795a808dc0c2ecbcf81608181e370 Mon Sep 17 00:00:00 2001
From: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Date: Sun, 9 Jun 2024 21:28:18 -0400
Subject: [PATCH] feat(vm2): add initial support for `cdrom` (#1370)
feat(vm2): add initial support for `cdrom`
This is a breaking change comparing to v1 - switching the cdrom schema from a nested block to a nested attribute map.
Improvements comparing to v1:
- support for `ide`, `sata`, `scsi` interfaces
- support for multiple cdroms
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
---
docs/resources/virtual_environment_vm2.md | 9 +
fwprovider/access/resource_user_token.go | 6 +
fwprovider/hardwaremapping/datasource.go | 8 +-
fwprovider/hardwaremapping/datasource_pci.go | 8 +-
fwprovider/hardwaremapping/datasource_usb.go | 8 +-
fwprovider/hardwaremapping/errors.go | 8 +-
fwprovider/hardwaremapping/models.go | 8 +-
fwprovider/hardwaremapping/resource_pci.go | 8 +-
fwprovider/hardwaremapping/resource_usb.go | 8 +-
fwprovider/hardwaremapping/shared.go | 6 +
fwprovider/validators/file.go | 22 +++
fwprovider/validators/hardware_mapping.go | 8 +-
fwprovider/vm/cdrom/model.go | 38 ++++
fwprovider/vm/cdrom/resource.go | 111 +++++++++++
fwprovider/vm/cdrom/resource_schema.go | 56 ++++++
fwprovider/vm/cdrom/resource_test.go | 172 ++++++++++++++++++
fwprovider/vm/cpu/datasource_schema.go | 2 +-
.../vm/cpu/{resource_model.go => model.go} | 6 +
fwprovider/vm/cpu/resource.go | 6 +
fwprovider/vm/cpu/resource_schema.go | 15 +-
fwprovider/vm/{vm_model.go => model.go} | 10 +
fwprovider/vm/resource.go | 10 +
fwprovider/vm/resource_schema.go | 10 +-
fwprovider/vm/vga/datasource_schema.go | 2 +-
.../vm/vga/{resource_model.go => model.go} | 0
fwprovider/vm/vga/resource.go | 6 +
proxmox/nodes/vms/custom_storage_device.go | 6 +-
.../nodes/vms/custom_storage_device_test.go | 3 +-
proxmoxtf/resource/vm/disk/disk.go | 7 +
utils/maps.go | 39 +++-
utils/sets.go | 45 +++++
utils/sets_test.go | 91 +++++++++
32 files changed, 694 insertions(+), 48 deletions(-)
create mode 100644 fwprovider/validators/file.go
create mode 100644 fwprovider/vm/cdrom/model.go
create mode 100644 fwprovider/vm/cdrom/resource.go
create mode 100644 fwprovider/vm/cdrom/resource_schema.go
create mode 100644 fwprovider/vm/cdrom/resource_test.go
rename fwprovider/vm/cpu/{resource_model.go => model.go} (83%)
rename fwprovider/vm/{vm_model.go => model.go} (88%)
rename fwprovider/vm/vga/{resource_model.go => model.go} (100%)
create mode 100644 utils/sets.go
create mode 100644 utils/sets_test.go
diff --git a/docs/resources/virtual_environment_vm2.md b/docs/resources/virtual_environment_vm2.md
index 375326f9..cc99ac60 100644
--- a/docs/resources/virtual_environment_vm2.md
+++ b/docs/resources/virtual_environment_vm2.md
@@ -30,6 +30,7 @@ The attributes are also marked as optional to allow the practitioner to set (or
### Optional
+- `cdrom` (Attributes Map) The CD-ROM configuration. The key is the interface of the CD-ROM, could be one of `ideN`, `sataN`, `scsiN`, where N is the index of the interface. (see [below for nested schema](#nestedatt--cdrom))
- `clone` (Attributes) The cloning configuration. (see [below for nested schema](#nestedatt--clone))
- `cpu` (Attributes) The CPU configuration. (see [below for nested schema](#nestedatt--cpu))
- `description` (String) The description of the VM.
@@ -40,6 +41,14 @@ The attributes are also marked as optional to allow the practitioner to set (or
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `vga` (Attributes) Configure the VGA Hardware. If you want to use high resolution modes (>= 1280x1024x16) you may need to increase the vga memory option. Since QEMU 2.9 the default VGA display type is `std` for all OS types besides some Windows versions (XP and older) which use `cirrus`. The `qxl` option enables the SPICE display server. For win* OS you can select how many independent displays you want, Linux guests can add displays themself. You can also run without any graphic card, using a serial device as terminal. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information and available configuration parameters. (see [below for nested schema](#nestedatt--vga))
+
+### Nested Schema for `cdrom`
+
+Optional:
+
+- `file_id` (String) The file ID of the CD-ROM, or `cdrom|none`. Defaults to `none` to leave the CD-ROM empty. Use `cdrom` to connect to the physical drive.
+
+
### Nested Schema for `clone`
diff --git a/fwprovider/access/resource_user_token.go b/fwprovider/access/resource_user_token.go
index bd0aafc4..bf487c3d 100644
--- a/fwprovider/access/resource_user_token.go
+++ b/fwprovider/access/resource_user_token.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package access
import (
diff --git a/fwprovider/hardwaremapping/datasource.go b/fwprovider/hardwaremapping/datasource.go
index 830a5627..cccc0bad 100644
--- a/fwprovider/hardwaremapping/datasource.go
+++ b/fwprovider/hardwaremapping/datasource.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/datasource_pci.go b/fwprovider/hardwaremapping/datasource_pci.go
index 237e6fdc..11edf44e 100644
--- a/fwprovider/hardwaremapping/datasource_pci.go
+++ b/fwprovider/hardwaremapping/datasource_pci.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/datasource_usb.go b/fwprovider/hardwaremapping/datasource_usb.go
index 4f4129fc..86df0ce7 100644
--- a/fwprovider/hardwaremapping/datasource_usb.go
+++ b/fwprovider/hardwaremapping/datasource_usb.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/errors.go b/fwprovider/hardwaremapping/errors.go
index 170d6da7..695f2768 100644
--- a/fwprovider/hardwaremapping/errors.go
+++ b/fwprovider/hardwaremapping/errors.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/models.go b/fwprovider/hardwaremapping/models.go
index 2dc2e7f0..66ca5015 100644
--- a/fwprovider/hardwaremapping/models.go
+++ b/fwprovider/hardwaremapping/models.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/resource_pci.go b/fwprovider/hardwaremapping/resource_pci.go
index 2c908878..c04b450e 100644
--- a/fwprovider/hardwaremapping/resource_pci.go
+++ b/fwprovider/hardwaremapping/resource_pci.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/resource_usb.go b/fwprovider/hardwaremapping/resource_usb.go
index 6df9e864..6546cc2c 100644
--- a/fwprovider/hardwaremapping/resource_usb.go
+++ b/fwprovider/hardwaremapping/resource_usb.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package hardwaremapping
diff --git a/fwprovider/hardwaremapping/shared.go b/fwprovider/hardwaremapping/shared.go
index 0d37bab7..cedfb43c 100644
--- a/fwprovider/hardwaremapping/shared.go
+++ b/fwprovider/hardwaremapping/shared.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package hardwaremapping
import (
diff --git a/fwprovider/validators/file.go b/fwprovider/validators/file.go
new file mode 100644
index 00000000..c651c42a
--- /dev/null
+++ b/fwprovider/validators/file.go
@@ -0,0 +1,22 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package validators
+
+import (
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+// FileID returns a validator that checks if a string is a valid file ID.
+func FileID() validator.String {
+ return stringvalidator.RegexMatches(
+ regexp.MustCompile(`^(?i)[a-z\d\-_.]+:([a-z\d\-_]+/)?.+$`),
+ "must be in the format `:/`",
+ )
+}
diff --git a/fwprovider/validators/hardware_mapping.go b/fwprovider/validators/hardware_mapping.go
index 6bebe11d..6cfa74a2 100644
--- a/fwprovider/validators/hardware_mapping.go
+++ b/fwprovider/validators/hardware_mapping.go
@@ -1,8 +1,8 @@
/*
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-*/
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
package validators
diff --git a/fwprovider/vm/cdrom/model.go b/fwprovider/vm/cdrom/model.go
new file mode 100644
index 00000000..b6c3db88
--- /dev/null
+++ b/fwprovider/vm/cdrom/model.go
@@ -0,0 +1,38 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package cdrom
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
+ "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
+)
+
+// Model represents the CD-ROM model.
+type Model struct {
+ FileID types.String `tfsdk:"file_id"`
+}
+
+func attributeTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "file_id": types.StringType,
+ }
+}
+
+func (m *Model) exportToCustomStorageDevice(iface string) vms.CustomStorageDevice {
+ return vms.CustomStorageDevice{
+ FileVolume: m.FileID.ValueString(),
+ Interface: &iface,
+ Media: ptr.Ptr("cdrom"),
+ }
+}
+
+func (m *Model) importFromCustomStorageDevice(d vms.CustomStorageDevice) {
+ m.FileID = types.StringValue(d.FileVolume)
+}
diff --git a/fwprovider/vm/cdrom/resource.go b/fwprovider/vm/cdrom/resource.go
new file mode 100644
index 00000000..e1eed7c5
--- /dev/null
+++ b/fwprovider/vm/cdrom/resource.go
@@ -0,0 +1,111 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package cdrom
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
+ "github.com/bpg/terraform-provider-proxmox/utils"
+)
+
+// Value represents the type for CD-ROM settings.
+type Value = types.Map
+
+// NewValue returns a new Value with the given CD-ROM settings from the PVE API.
+func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value {
+ // find storage devices with media=cdrom
+ cdroms := vms.MapCustomStorageDevices(*config).Filter(func(device *vms.CustomStorageDevice) bool {
+ return device.Media != nil && *device.Media == "cdrom"
+ })
+
+ elements := make(map[string]Model, len(cdroms))
+
+ for iface, cdrom := range cdroms {
+ m := Model{}
+ m.importFromCustomStorageDevice(*cdrom)
+ elements[iface] = m
+ }
+
+ obj, d := types.MapValueFrom(ctx, types.ObjectType{}.WithAttributeTypes(attributeTypes()), elements)
+ diags.Append(d...)
+
+ return obj
+}
+
+// FillCreateBody fills the CreateRequestBody with the CD-ROM settings from the Value.
+//
+// In the 'create' context, v is the plan.
+func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
+ if planValue.IsNull() || planValue.IsUnknown() {
+ return
+ }
+
+ var plan map[string]Model
+ d := planValue.ElementsAs(ctx, &plan, false)
+ diags.Append(d...)
+
+ if d.HasError() {
+ return
+ }
+
+ for iface, cdrom := range plan {
+ err := body.AddCustomStorageDevice(cdrom.exportToCustomStorageDevice(iface))
+ if err != nil {
+ diags.AddError(err.Error(), "")
+ }
+ }
+}
+
+// FillUpdateBody fills the UpdateRequestBody with the CD-ROM settings from the Value.
+//
+// In the 'update' context, v is the plan and stateValue is the current state.
+func FillUpdateBody(
+ ctx context.Context,
+ planValue, stateValue Value,
+ updateBody *vms.UpdateRequestBody,
+ _ bool,
+ diags *diag.Diagnostics,
+) {
+ if planValue.IsNull() || planValue.IsUnknown() || planValue.Equal(stateValue) {
+ return
+ }
+
+ var plan, state map[string]Model
+ d := planValue.ElementsAs(ctx, &plan, false)
+ diags.Append(d...)
+ d = stateValue.ElementsAs(ctx, &state, false)
+ diags.Append(d...)
+
+ if diags.HasError() {
+ return
+ }
+
+ toCreate, toUpdate, toDelete := utils.MapDiff(plan, state)
+
+ for iface, dev := range toCreate {
+ err := updateBody.AddCustomStorageDevice(dev.exportToCustomStorageDevice(iface))
+ if err != nil {
+ diags.AddError(err.Error(), "")
+ }
+ }
+
+ for iface, dev := range toUpdate {
+ // for CD-ROMs, the update fully override the existing device, we don't do per-attribute check
+ err := updateBody.AddCustomStorageDevice(dev.exportToCustomStorageDevice(iface))
+ if err != nil {
+ diags.AddError(err.Error(), "")
+ }
+ }
+
+ for iface := range toDelete {
+ updateBody.Delete = append(updateBody.Delete, iface)
+ }
+}
diff --git a/fwprovider/vm/cdrom/resource_schema.go b/fwprovider/vm/cdrom/resource_schema.go
new file mode 100644
index 00000000..69c529f9
--- /dev/null
+++ b/fwprovider/vm/cdrom/resource_schema.go
@@ -0,0 +1,56 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package cdrom
+
+import (
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
+)
+
+// ResourceSchema defines the schema for the CD-ROM resource.
+func ResourceSchema() schema.Attribute {
+ return schema.MapNestedAttribute{
+ Description: "The CD-ROM configuration",
+ MarkdownDescription: "The CD-ROM configuration. The key is the interface of the CD-ROM, " +
+ "could be one of `ideN`, `sataN`, `scsiN`, where N is the index of the interface.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Map{
+ mapvalidator.KeysAre(
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^(ide[0-3]|sata[0-5]|scsi([0-9]|1[0-3]))$`),
+ "one of `ide[0-3]`, `sata[0-5]`, `scsi[0-13]`",
+ ),
+ ),
+ },
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "file_id": schema.StringAttribute{
+ Description: "The file ID of the CD-ROM",
+ MarkdownDescription: "The file ID of the CD-ROM, or `cdrom|none`." +
+ " Defaults to `none` to leave the CD-ROM empty. Use `cdrom` to connect to the physical drive.",
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString("cdrom"),
+ Validators: []validator.String{
+ stringvalidator.Any(
+ stringvalidator.OneOf("cdrom", "none"),
+ validators.FileID(),
+ ),
+ },
+ },
+ },
+ },
+ }
+}
diff --git a/fwprovider/vm/cdrom/resource_test.go b/fwprovider/vm/cdrom/resource_test.go
new file mode 100644
index 00000000..5f6724f4
--- /dev/null
+++ b/fwprovider/vm/cdrom/resource_test.go
@@ -0,0 +1,172 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package cdrom_test
+
+import (
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/test"
+)
+
+const resourceName = "proxmox_virtual_environment_vm2.test_vm"
+
+func TestAccResourceVM2CDROM(t *testing.T) {
+ t.Parallel()
+
+ te := test.InitEnvironment(t)
+
+ tests := []struct {
+ name string
+ steps []resource.TestStep
+ }{
+ {"create VM default CDROM", []resource.TestStep{{
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID}}
+ name = "test-cdrom"
+ cdrom = {
+ "ide3" = {}
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "1",
+ "cdrom.ide3.file_id": "cdrom",
+ }),
+ }}},
+ {"create VM multiple CDROMs", []resource.TestStep{{
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID}}
+ name = "test-cdrom"
+ cdrom = {
+ "ide3" = {},
+ "ide1" = {
+ file_id = "none"
+ }
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "2",
+ "cdrom.ide3.file_id": "cdrom",
+ "cdrom.ide1.file_id": "none",
+ }),
+ }}},
+ {"create VM with CDROM and then update it", []resource.TestStep{
+ {
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID}}
+ name = "test-cdrom"
+ cdrom = {
+ "scsi2" = {
+ file_id = "none"
+ },
+ "ide2" = {
+ file_id = "cdrom"
+ }
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "2",
+ "cdrom.scsi2.file_id": "none",
+ "cdrom.ide2.file_id": "cdrom",
+ }),
+ },
+ { // now update the cdrom params and check if they are updated
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cdrom"
+ cdrom = {
+ "scsi2" = {
+ file_id = "cdrom"
+ }
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "1",
+ "cdrom.scsi2.file_id": "cdrom",
+ }),
+ },
+ {
+ RefreshState: true,
+ },
+ }},
+ {"clone VM with CDROM", []resource.TestStep{{
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "template_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID1}}
+ name = "template-cdrom"
+ cdrom = {
+ "ide3" = {
+ file_id = "cdrom"
+ }
+ }
+ }
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID2}}
+ name = "test-cdrom"
+ clone = {
+ id = proxmox_virtual_environment_vm2.template_vm.id
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "1",
+ "cdrom.ide3.file_id": "cdrom",
+ }),
+ }}},
+ {"clone VM with some CDROM params and updating them in the clone", []resource.TestStep{{
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm2" "template_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID1}}
+ name = "template-cdrom"
+ cdrom = {
+ "ide1" = {
+ file_id = "none"
+ },
+ "ide2" = {
+ file_id = "cdrom"
+ }
+ }
+ }
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ id = {{.RandomVMID2}}
+ name = "test-cpu"
+ clone = {
+ id = proxmox_virtual_environment_vm2.template_vm.id
+ }
+ cdrom = {
+ "ide1" = {
+ file_id = "cdrom"
+ }
+ }
+ }`),
+ Check: test.ResourceAttributes(resourceName, map[string]string{
+ "cdrom.%": "1",
+ "cdrom.ide1.file_id": "cdrom",
+ }),
+ }}},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resource.ParallelTest(t, resource.TestCase{
+ ProtoV6ProviderFactories: te.AccProviders,
+ Steps: tt.steps,
+ })
+ })
+ }
+}
diff --git a/fwprovider/vm/cpu/datasource_schema.go b/fwprovider/vm/cpu/datasource_schema.go
index 33f48fc7..4f8f300d 100644
--- a/fwprovider/vm/cpu/datasource_schema.go
+++ b/fwprovider/vm/cpu/datasource_schema.go
@@ -6,7 +6,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
-// DataSourceSchema defines the schema for the CPU resource.
+// DataSourceSchema defines the schema for the CPU datasource.
func DataSourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
CustomType: basetypes.ObjectType{
diff --git a/fwprovider/vm/cpu/resource_model.go b/fwprovider/vm/cpu/model.go
similarity index 83%
rename from fwprovider/vm/cpu/resource_model.go
rename to fwprovider/vm/cpu/model.go
index 62490e3e..37e8a346 100644
--- a/fwprovider/vm/cpu/resource_model.go
+++ b/fwprovider/vm/cpu/model.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package cpu
import (
diff --git a/fwprovider/vm/cpu/resource.go b/fwprovider/vm/cpu/resource.go
index b7e3abf8..9b715850 100644
--- a/fwprovider/vm/cpu/resource.go
+++ b/fwprovider/vm/cpu/resource.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package cpu
import (
diff --git a/fwprovider/vm/cpu/resource_schema.go b/fwprovider/vm/cpu/resource_schema.go
index abafcca5..33be008b 100644
--- a/fwprovider/vm/cpu/resource_schema.go
+++ b/fwprovider/vm/cpu/resource_schema.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package cpu
import (
@@ -8,25 +14,16 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
// ResourceSchema defines the schema for the CPU resource.
func ResourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
- CustomType: basetypes.ObjectType{
- AttrTypes: attributeTypes(),
- },
Description: "The CPU configuration.",
Optional: true,
Computed: true,
- PlanModifiers: []planmodifier.Object{
- objectplanmodifier.UseStateForUnknown(),
- },
Attributes: map[string]schema.Attribute{
"affinity": schema.StringAttribute{
Description: "List of host cores used to execute guest processes, for example: '0,5,8-11'",
diff --git a/fwprovider/vm/vm_model.go b/fwprovider/vm/model.go
similarity index 88%
rename from fwprovider/vm/vm_model.go
rename to fwprovider/vm/model.go
index 5943507d..7148e48f 100644
--- a/fwprovider/vm/vm_model.go
+++ b/fwprovider/vm/model.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package vm
import (
@@ -10,6 +16,7 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
"github.com/bpg/terraform-provider-proxmox/proxmox"
@@ -22,6 +29,7 @@ import (
// or a custom type in order to hold an unknown value.
type Model struct {
Description types.String `tfsdk:"description"`
+ CDROM cdrom.Value `tfsdk:"cdrom"`
CPU cpu.Value `tfsdk:"cpu"`
Clone *struct {
ID types.Int64 `tfsdk:"id"`
@@ -78,5 +86,7 @@ func read(ctx context.Context, client proxmox.Client, model *Model, diags *diag.
model.CPU = cpu.NewValue(ctx, config, diags)
model.VGA = vga.NewValue(ctx, config, diags)
+ model.CDROM = cdrom.NewValue(ctx, config, diags)
+
return true
}
diff --git a/fwprovider/vm/resource.go b/fwprovider/vm/resource.go
index d88997ad..26e90e6f 100644
--- a/fwprovider/vm/resource.go
+++ b/fwprovider/vm/resource.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package vm
import (
@@ -15,6 +21,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
"github.com/bpg/terraform-provider-proxmox/proxmox"
@@ -146,6 +153,7 @@ func (r *Resource) create(ctx context.Context, plan Model, diags *diag.Diagnosti
}
// fill out create body fields with values from other resource blocks
+ cdrom.FillCreateBody(ctx, plan.CDROM, createBody, diags)
cpu.FillCreateBody(ctx, plan.CPU, createBody, diags)
vga.FillCreateBody(ctx, plan.VGA, createBody, diags)
@@ -322,6 +330,8 @@ func (r *Resource) update(ctx context.Context, plan, state Model, isClone bool,
}
}
+ // fill out update body fields with values from other resource blocks
+ cdrom.FillUpdateBody(ctx, plan.CDROM, state.CDROM, updateBody, isClone, diags)
cpu.FillUpdateBody(ctx, plan.CPU, state.CPU, updateBody, isClone, diags)
vga.FillUpdateBody(ctx, plan.VGA, state.VGA, updateBody, isClone, diags)
diff --git a/fwprovider/vm/resource_schema.go b/fwprovider/vm/resource_schema.go
index 4e270d68..d6170684 100644
--- a/fwprovider/vm/resource_schema.go
+++ b/fwprovider/vm/resource_schema.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package vm
import (
@@ -16,6 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
)
@@ -51,7 +58,8 @@ func (r *Resource) Schema(
},
},
},
- "cpu": cpu.ResourceSchema(),
+ "cdrom": cdrom.ResourceSchema(),
+ "cpu": cpu.ResourceSchema(),
"description": schema.StringAttribute{
Description: "The description of the VM.",
Optional: true,
diff --git a/fwprovider/vm/vga/datasource_schema.go b/fwprovider/vm/vga/datasource_schema.go
index fa88316d..d0d2f6b7 100644
--- a/fwprovider/vm/vga/datasource_schema.go
+++ b/fwprovider/vm/vga/datasource_schema.go
@@ -5,7 +5,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
-// DataSourceSchema defines the schema for the VGA resource.
+// DataSourceSchema defines the schema for the VGA datasource.
func DataSourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
CustomType: basetypes.ObjectType{
diff --git a/fwprovider/vm/vga/resource_model.go b/fwprovider/vm/vga/model.go
similarity index 100%
rename from fwprovider/vm/vga/resource_model.go
rename to fwprovider/vm/vga/model.go
diff --git a/fwprovider/vm/vga/resource.go b/fwprovider/vm/vga/resource.go
index fc4abe24..dfbb54e2 100644
--- a/fwprovider/vm/vga/resource.go
+++ b/fwprovider/vm/vga/resource.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package vga
import (
diff --git a/proxmox/nodes/vms/custom_storage_device.go b/proxmox/nodes/vms/custom_storage_device.go
index 957f5e8e..60e9ed21 100644
--- a/proxmox/nodes/vms/custom_storage_device.go
+++ b/proxmox/nodes/vms/custom_storage_device.go
@@ -400,6 +400,8 @@ func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
}
// MapCustomStorageDevices maps the custom storage devices from the API response.
+//
+// NOTE: CustomStorageDevice.FileID and CustomStorageDevice.DatastoreID are not set in this function.
func MapCustomStorageDevices(resp GetResponseData) CustomStorageDevices {
csd := CustomStorageDevices{}
@@ -411,13 +413,13 @@ func MapCustomStorageDevices(resp GetResponseData) CustomStorageDevices {
return csd
}
-func mapDevice(csd CustomStorageDevices, resp GetResponseData, keyPrefix, fieldPrefix string, end int) {
+func mapDevice(csds CustomStorageDevices, resp GetResponseData, keyPrefix, fieldPrefix string, end int) {
for i := 0; i <= end; i++ {
field := reflect.ValueOf(resp).FieldByName(fieldPrefix + "Device" + strconv.Itoa(i))
if !field.IsZero() {
val := field.Interface()
if val != nil {
- csd[keyPrefix+strconv.Itoa(i)] = val.(*CustomStorageDevice)
+ csds[keyPrefix+strconv.Itoa(i)] = val.(*CustomStorageDevice)
}
}
}
diff --git a/proxmox/nodes/vms/custom_storage_device_test.go b/proxmox/nodes/vms/custom_storage_device_test.go
index 51a05c62..0301bfa7 100644
--- a/proxmox/nodes/vms/custom_storage_device_test.go
+++ b/proxmox/nodes/vms/custom_storage_device_test.go
@@ -267,7 +267,8 @@ func TestMapCustomStorageDevices(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- assert.Equalf(t, tt.want, MapCustomStorageDevices(tt.args.resp), "MapCustomStorageDevices(%v)", tt.args.resp)
+ devices := MapCustomStorageDevices(tt.args.resp)
+ assert.Equalf(t, tt.want, devices, "MapCustomStorageDevices(%v)", tt.args.resp)
})
}
}
diff --git a/proxmoxtf/resource/vm/disk/disk.go b/proxmoxtf/resource/vm/disk/disk.go
index 1373e35a..d2b7fda5 100644
--- a/proxmoxtf/resource/vm/disk/disk.go
+++ b/proxmoxtf/resource/vm/disk/disk.go
@@ -1,3 +1,9 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package disk
import (
@@ -23,6 +29,7 @@ import (
const supportedDiskInterfaces = "virtio, sata, scsi, ide"
// GetInfo returns the disk information for a VM.
+// Deprecated: use vms.MapCustomStorageDevices from proxmox/nodes/vms instead.
func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorageDevices {
storageDevices := vms.CustomStorageDevices{}
diff --git a/utils/maps.go b/utils/maps.go
index ba17b9df..e67cbbfd 100644
--- a/utils/maps.go
+++ b/utils/maps.go
@@ -1,6 +1,15 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
package utils
-import "sort"
+import (
+ "reflect"
+ "sort"
+)
// OrderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys.
func OrderedListFromMap(inputMap map[string]interface{}) []interface{} {
@@ -71,3 +80,31 @@ func OrderedListFromMapByKeyValues(inputMap map[string]interface{}, keyList []st
return orderedList
}
+
+// MapDiff compares the difference between two maps and returns the elements that are in the plan but not
+// in the state (toCreate), the elements that are in the plan and in the state but are different (toUpdate),
+// and the elements that are in the state but not in the plan (toDelete).
+// The keyFunc is used to extract a unique key from each element to compare them.
+func MapDiff[T any](plan map[string]T, state map[string]T) (map[string]T, map[string]T, map[string]T) {
+ toCreate := map[string]T{}
+ toUpdate := map[string]T{}
+ toDelete := map[string]T{}
+
+ for key, p := range plan {
+ s, ok := state[key]
+ if !ok {
+ toCreate[key] = p
+ } else if !reflect.DeepEqual(p, s) {
+ toUpdate[key] = p
+ }
+ }
+
+ for key, s := range state {
+ _, ok := plan[key]
+ if !ok {
+ toDelete[key] = s
+ }
+ }
+
+ return toCreate, toUpdate, toDelete
+}
diff --git a/utils/sets.go b/utils/sets.go
new file mode 100644
index 00000000..92ff58eb
--- /dev/null
+++ b/utils/sets.go
@@ -0,0 +1,45 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package utils
+
+import "reflect"
+
+// SetDiff compares two slices of elements and returns the elements that are in the plan but not
+// in the state (toCreate), the elements that are in the plan and in the state but are different (toUpdate),
+// and the elements that are in the state but not in the plan (toDelete).
+// The keyFunc is used to extract a unique key from each element to compare them.
+func SetDiff[T any](plan []T, state []T, keyFunc func(t T) string) ([]T, []T, []T) {
+ var toCreate, toUpdate, toDelete []T
+
+ stateMap := map[string]T{}
+ for _, s := range state {
+ stateMap[keyFunc(s)] = s
+ }
+
+ planMap := map[string]T{}
+ for _, p := range plan {
+ planMap[keyFunc(p)] = p
+ }
+
+ for _, p := range plan {
+ s, ok := stateMap[keyFunc(p)]
+ if !ok {
+ toCreate = append(toCreate, p)
+ } else if !reflect.DeepEqual(p, s) {
+ toUpdate = append(toUpdate, p)
+ }
+ }
+
+ for _, s := range state {
+ _, ok := planMap[keyFunc(s)]
+ if !ok {
+ toDelete = append(toDelete, s)
+ }
+ }
+
+ return toCreate, toUpdate, toDelete
+}
diff --git a/utils/sets_test.go b/utils/sets_test.go
new file mode 100644
index 00000000..5f605405
--- /dev/null
+++ b/utils/sets_test.go
@@ -0,0 +1,91 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package utils
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSetDiff(t *testing.T) {
+ t.Parallel()
+
+ type args[T any] struct {
+ plan []T
+ state []T
+ }
+
+ type testCase[T any] struct {
+ name string
+ args args[T]
+ wantToCreate []T
+ wantToUpdate []T
+ wantToDelete []T
+ }
+
+ type rec struct {
+ n string
+ v string
+ }
+
+ tests := []testCase[rec]{
+ {
+ name: "empty",
+ args: args[rec]{
+ plan: []rec{},
+ state: []rec{},
+ },
+ wantToCreate: nil,
+ wantToUpdate: nil,
+ wantToDelete: nil,
+ },
+ {
+ name: "create",
+ args: args[rec]{
+ plan: []rec{{"a", "1"}, {"b", "2"}, {"c", "3"}},
+ state: []rec{},
+ },
+ wantToCreate: []rec{{"a", "1"}, {"b", "2"}, {"c", "3"}},
+ wantToUpdate: nil,
+ wantToDelete: nil,
+ },
+ {
+ name: "create and delete",
+ args: args[rec]{
+ plan: []rec{{"a", "1"}, {"b", "2"}, {"c", "3"}},
+ state: []rec{{"b", "2"}, {"c", "3"}, {"d", "4"}},
+ },
+ wantToCreate: []rec{{"a", "1"}},
+ wantToUpdate: nil,
+ wantToDelete: []rec{{"d", "4"}},
+ },
+ {
+ name: "update",
+ args: args[rec]{
+ plan: []rec{{"a", "1"}, {"b", "2"}, {"c", "3"}},
+ state: []rec{{"a", "1"}, {"b", "2"}, {"c", "4"}},
+ },
+ wantToCreate: nil,
+ wantToUpdate: []rec{{"c", "3"}},
+ wantToDelete: nil,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ gotToCreate, gotToUpdate, gotToDelete := SetDiff(
+ tt.args.plan, tt.args.state, func(t rec) string { return t.n },
+ )
+ assert.Equalf(t, tt.wantToCreate, gotToCreate, "toCreate is different")
+ assert.Equalf(t, tt.wantToUpdate, gotToUpdate, "toUpdate is different")
+ assert.Equalf(t, tt.wantToDelete, gotToDelete, "toDelete is different")
+ })
+ }
+}