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:
parent
580381f892
commit
80cafa689f
@ -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`
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 (
|
||||
|
22
fwprovider/validators/file.go
Normal file
22
fwprovider/validators/file.go
Normal 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>`",
|
||||
)
|
||||
}
|
@ -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
|
||||
|
||||
|
38
fwprovider/vm/cdrom/model.go
Normal file
38
fwprovider/vm/cdrom/model.go
Normal 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)
|
||||
}
|
111
fwprovider/vm/cdrom/resource.go
Normal file
111
fwprovider/vm/cdrom/resource.go
Normal 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)
|
||||
}
|
||||
}
|
56
fwprovider/vm/cdrom/resource_schema.go
Normal file
56
fwprovider/vm/cdrom/resource_schema.go
Normal 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(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
172
fwprovider/vm/cdrom/resource_test.go
Normal file
172
fwprovider/vm/cdrom/resource_test.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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 (
|
@ -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 (
|
||||
|
@ -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'",
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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{
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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
45
utils/sets.go
Normal 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
91
utils/sets_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user