0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00

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>
This commit is contained in:
Pavel Boldyrev 2024-06-09 21:28:18 -04:00 committed by GitHub
parent 580381f892
commit 80cafa689f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 694 additions and 48 deletions

View File

@ -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))
<a id="nestedatt--cdrom"></a>
### 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.
<a id="nestedatt--clone"></a>
### Nested Schema for `clone`

View File

@ -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 (

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 (

View File

@ -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 `<datastore name>:<content type>/<file name>`",
)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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(),
),
},
},
},
},
}
}

View File

@ -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,
})
})
}
}

View File

@ -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{

View File

@ -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 (

View File

@ -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 (

View File

@ -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'",

View File

@ -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
}

View File

@ -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)

View File

@ -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,

View File

@ -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{

View File

@ -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 (

View File

@ -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)
}
}
}

View File

@ -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)
})
}
}

View File

@ -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{}

View File

@ -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
}

45
utils/sets.go Normal file
View File

@ -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
}

91
utils/sets_test.go Normal file
View File

@ -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")
})
}
}