mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31: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
|
### 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))
|
- `clone` (Attributes) The cloning configuration. (see [below for nested schema](#nestedatt--clone))
|
||||||
- `cpu` (Attributes) The CPU configuration. (see [below for nested schema](#nestedatt--cpu))
|
- `cpu` (Attributes) The CPU configuration. (see [below for nested schema](#nestedatt--cpu))
|
||||||
- `description` (String) The description of the VM.
|
- `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))
|
- `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))
|
- `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>
|
<a id="nestedatt--clone"></a>
|
||||||
### Nested Schema for `clone`
|
### 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
|
package access
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
package hardwaremapping
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package hardwaremapping
|
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
|
package hardwaremapping
|
||||||
|
|
||||||
import (
|
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
|
* 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
|
* 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/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package validators
|
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"
|
"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 {
|
func DataSourceSchema() schema.Attribute {
|
||||||
return schema.SingleNestedAttribute{
|
return schema.SingleNestedAttribute{
|
||||||
CustomType: basetypes.ObjectType{
|
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
|
package cpu
|
||||||
|
|
||||||
import (
|
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
|
package cpu
|
||||||
|
|
||||||
import (
|
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
|
package cpu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -8,25 +14,16 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
"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/schema/validator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceSchema defines the schema for the CPU resource.
|
// ResourceSchema defines the schema for the CPU resource.
|
||||||
func ResourceSchema() schema.Attribute {
|
func ResourceSchema() schema.Attribute {
|
||||||
return schema.SingleNestedAttribute{
|
return schema.SingleNestedAttribute{
|
||||||
CustomType: basetypes.ObjectType{
|
|
||||||
AttrTypes: attributeTypes(),
|
|
||||||
},
|
|
||||||
Description: "The CPU configuration.",
|
Description: "The CPU configuration.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
PlanModifiers: []planmodifier.Object{
|
|
||||||
objectplanmodifier.UseStateForUnknown(),
|
|
||||||
},
|
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"affinity": schema.StringAttribute{
|
"affinity": schema.StringAttribute{
|
||||||
Description: "List of host cores used to execute guest processes, for example: '0,5,8-11'",
|
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
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -10,6 +16,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
"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/cpu"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||||
@ -22,6 +29,7 @@ import (
|
|||||||
// or a custom type in order to hold an unknown value.
|
// or a custom type in order to hold an unknown value.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Description types.String `tfsdk:"description"`
|
Description types.String `tfsdk:"description"`
|
||||||
|
CDROM cdrom.Value `tfsdk:"cdrom"`
|
||||||
CPU cpu.Value `tfsdk:"cpu"`
|
CPU cpu.Value `tfsdk:"cpu"`
|
||||||
Clone *struct {
|
Clone *struct {
|
||||||
ID types.Int64 `tfsdk:"id"`
|
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.CPU = cpu.NewValue(ctx, config, diags)
|
||||||
model.VGA = vga.NewValue(ctx, config, diags)
|
model.VGA = vga.NewValue(ctx, config, diags)
|
||||||
|
|
||||||
|
model.CDROM = cdrom.NewValue(ctx, config, diags)
|
||||||
|
|
||||||
return true
|
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
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -15,6 +21,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
"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/cpu"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"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
|
// 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)
|
cpu.FillCreateBody(ctx, plan.CPU, createBody, diags)
|
||||||
vga.FillCreateBody(ctx, plan.VGA, 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)
|
cpu.FillUpdateBody(ctx, plan.CPU, state.CPU, updateBody, isClone, diags)
|
||||||
vga.FillUpdateBody(ctx, plan.VGA, state.VGA, 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
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -16,6 +22,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
"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/cpu"
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
"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": schema.StringAttribute{
|
||||||
Description: "The description of the VM.",
|
Description: "The description of the VM.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
"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 {
|
func DataSourceSchema() schema.Attribute {
|
||||||
return schema.SingleNestedAttribute{
|
return schema.SingleNestedAttribute{
|
||||||
CustomType: basetypes.ObjectType{
|
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
|
package vga
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -400,6 +400,8 @@ func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MapCustomStorageDevices maps the custom storage devices from the API response.
|
// 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 {
|
func MapCustomStorageDevices(resp GetResponseData) CustomStorageDevices {
|
||||||
csd := CustomStorageDevices{}
|
csd := CustomStorageDevices{}
|
||||||
|
|
||||||
@ -411,13 +413,13 @@ func MapCustomStorageDevices(resp GetResponseData) CustomStorageDevices {
|
|||||||
return csd
|
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++ {
|
for i := 0; i <= end; i++ {
|
||||||
field := reflect.ValueOf(resp).FieldByName(fieldPrefix + "Device" + strconv.Itoa(i))
|
field := reflect.ValueOf(resp).FieldByName(fieldPrefix + "Device" + strconv.Itoa(i))
|
||||||
if !field.IsZero() {
|
if !field.IsZero() {
|
||||||
val := field.Interface()
|
val := field.Interface()
|
||||||
if val != nil {
|
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.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
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
|
package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -23,6 +29,7 @@ import (
|
|||||||
const supportedDiskInterfaces = "virtio, sata, scsi, ide"
|
const supportedDiskInterfaces = "virtio, sata, scsi, ide"
|
||||||
|
|
||||||
// GetInfo returns the disk information for a VM.
|
// 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 {
|
func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorageDevices {
|
||||||
storageDevices := 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
|
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.
|
// 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{} {
|
func OrderedListFromMap(inputMap map[string]interface{}) []interface{} {
|
||||||
@ -71,3 +80,31 @@ func OrderedListFromMapByKeyValues(inputMap map[string]interface{}, keyList []st
|
|||||||
|
|
||||||
return orderedList
|
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