From aa309fd9eabad00e7694b8bd7d63cad6180d7118 Mon Sep 17 00:00:00 2001
From: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Date: Sat, 18 May 2024 00:52:02 -0400
Subject: [PATCH] chore(vm2): add support for `cpu` (#1310)
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
---
docs/resources/virtual_environment_vm2.md | 18 +
fwprovider/resource_options.go | 6 +-
fwprovider/tests/resource_time_test.go | 63 ++++
fwprovider/tests/resource_vm2_test.go | 181 +++++++++
.../types/{tags => stringset}/attribute.go | 15 +-
fwprovider/types/{tags => stringset}/type.go | 12 +-
fwprovider/types/{tags => stringset}/value.go | 32 +-
fwprovider/vm/cpu/resource_cpu.go | 347 ++++++++++++++++++
fwprovider/vm/cpu/resource_cpu_model.go | 35 ++
fwprovider/vm/cpu/resource_cpu_schema.go | 218 +++++++++++
fwprovider/vm/resource_model.go | 22 --
fwprovider/vm/{resource.go => resource_vm.go} | 94 +++--
fwprovider/vm/resource_vm_model.go | 26 ++
...source_schema.go => resource_vm_schema.go} | 8 +-
proxmox/nodes/vms/vms_types.go | 65 ++--
proxmox/types/common_types.go | 5 +
proxmoxtf/resource/vm/vm.go | 40 +-
qodana.yaml | 1 +
18 files changed, 1045 insertions(+), 143 deletions(-)
create mode 100644 fwprovider/tests/resource_time_test.go
rename fwprovider/types/{tags => stringset}/attribute.go (71%)
rename fwprovider/types/{tags => stringset}/type.go (83%)
rename fwprovider/types/{tags => stringset}/value.go (68%)
create mode 100644 fwprovider/vm/cpu/resource_cpu.go
create mode 100644 fwprovider/vm/cpu/resource_cpu_model.go
create mode 100644 fwprovider/vm/cpu/resource_cpu_schema.go
delete mode 100644 fwprovider/vm/resource_model.go
rename fwprovider/vm/{resource.go => resource_vm.go} (80%)
create mode 100644 fwprovider/vm/resource_vm_model.go
rename fwprovider/vm/{resource_schema.go => resource_vm_schema.go} (92%)
diff --git a/docs/resources/virtual_environment_vm2.md b/docs/resources/virtual_environment_vm2.md
index 29dffc05..68e48461 100644
--- a/docs/resources/virtual_environment_vm2.md
+++ b/docs/resources/virtual_environment_vm2.md
@@ -33,6 +33,7 @@ In order to remove the computed attribute from the plan, you can set it to an em
### Optional
- `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.
- `id` (Number) The unique identifier of the VM in the Proxmox cluster.
- `name` (String) The name of the VM. Doesn't have to be unique.
@@ -52,6 +53,23 @@ Optional:
- `retries` (Number) The number of retries to perform when cloning the VM (default: 3).
+
+### Nested Schema for `cpu`
+
+Optional:
+
+- `affinity` (String) The CPU cores that are used to run the VM’s vCPU. The value is a list of CPU IDs, separated by commas. The CPU IDs are zero-based. For example, `0,1,2,3` (which also can be shortened to `0-3`) means that the VM’s vCPUs are run on the first four CPU cores. Setting `affinity` is only allowed for `root@pam` authenticated user.
+- `architecture` (String) The CPU architecture `` (defaults to the host). Setting `affinity` is only allowed for `root@pam` authenticated user.
+- `cores` (Number) The number of CPU cores per socket (defaults to `1`).
+- `flags` (Set of String) Set of additional CPU flags. Use `+FLAG` to enable, `-FLAG` to disable a flag. Custom CPU models can specify any flag supported by QEMU/KVM, VM-specific flags must be from the following set for security reasons: `pcid`, `spec-ctrl`, `ibpb`, `ssbd`, `virt-ssbd`, `amd-ssbd`, `amd-no-ssb`, `pdpe1gb`, `md-clear`, `hv-tlbflush`, `hv-evmcs`, `aes`.
+- `hotplugged` (Number) The number of hotplugged vCPUs (defaults to `0`).
+- `limit` (Number) Limit of CPU usage (defaults to `0` which means no limit).
+- `numa` (Boolean) Enable NUMA (defaults to `false`).
+- `sockets` (Number) The number of CPU sockets (defaults to `1`).
+- `type` (String) Emulated CPU type, it's recommended to use `x86-64-v2-AES` or higher (defaults to `kvm64`). See https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings for more information.
+- `units` (Number) CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.
+
+
### Nested Schema for `timeouts`
diff --git a/fwprovider/resource_options.go b/fwprovider/resource_options.go
index 2f651d29..89c84f04 100644
--- a/fwprovider/resource_options.go
+++ b/fwprovider/resource_options.go
@@ -405,10 +405,8 @@ func (m *clusterOptionsModel) importFromOptionsAPI(_ context.Context, opts *clus
if opts.NextID != nil {
m.NextID = &clusterOptionsNextIDModel{}
- lower := int64(*opts.NextID.Lower)
- upper := int64(*opts.NextID.Upper)
- m.NextID.Lower = types.Int64PointerValue(&lower)
- m.NextID.Upper = types.Int64PointerValue(&upper)
+ m.NextID.Lower = types.Int64PointerValue(opts.NextID.Lower.PointerInt64())
+ m.NextID.Upper = types.Int64PointerValue(opts.NextID.Upper.PointerInt64())
}
if opts.Notify != nil {
diff --git a/fwprovider/tests/resource_time_test.go b/fwprovider/tests/resource_time_test.go
new file mode 100644
index 00000000..9ea6995c
--- /dev/null
+++ b/fwprovider/tests/resource_time_test.go
@@ -0,0 +1,63 @@
+/*
+ * 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 tests
+
+import (
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccResourceTime(t *testing.T) {
+ t.Parallel()
+
+ te := initTestEnvironment(t)
+
+ tests := []struct {
+ name string
+ steps []resource.TestStep
+ }{
+ {"change timezone", []resource.TestStep{
+ {
+ Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
+ node_name = "{{.NodeName}}"
+ time_zone = "America/New_York"
+ }`),
+ Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
+ "time_zone": "America/New_York",
+ }),
+ },
+ {
+ Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
+ node_name = "{{.NodeName}}"
+ time_zone = "UTC"
+ }`),
+ Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
+ "time_zone": "UTC",
+ }),
+ },
+ {
+ Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
+ node_name = "{{.NodeName}}"
+ time_zone = "UTC"
+ }`),
+ Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
+ "time_zone": "UTC",
+ }),
+ },
+ }},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: te.accProviders,
+ Steps: tt.steps,
+ })
+ })
+ }
+}
diff --git a/fwprovider/tests/resource_vm2_test.go b/fwprovider/tests/resource_vm2_test.go
index 015c031f..f0591c6e 100644
--- a/fwprovider/tests/resource_vm2_test.go
+++ b/fwprovider/tests/resource_vm2_test.go
@@ -201,6 +201,187 @@ func TestAccResourceVM2(t *testing.T) {
}
}
+func TestAccResourceVM2CPU(t *testing.T) {
+ t.Parallel()
+
+ te := initTestEnvironment(t)
+
+ tests := []struct {
+ name string
+ steps []resource.TestStep
+ }{
+ {"create VM with no cpu params", []resource.TestStep{{
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ // default values that are set by PVE if not specified
+ "cpu.cores": "1",
+ "cpu.sockets": "1",
+ "cpu.type": "kvm64",
+ }),
+ ),
+ }}},
+ {"create VM with some cpu params", []resource.TestStep{{
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ cpu = {
+ cores = 2
+ sockets = 2
+ type = "host"
+ flags = ["+aes"]
+ }
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ "cpu.cores": "2",
+ "cpu.sockets": "2",
+ "cpu.type": "host",
+ "cpu.flags.#": "1",
+ "cpu.flags.0": `\+aes`,
+ }),
+ ),
+ }}},
+ {"create VM with all cpu params and then update them", []resource.TestStep{
+ {
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ cpu = {
+ # affinity = "0-1" only root can set affinity
+ # architecture = "x86_64" only root can set architecture
+ cores = 2
+ hotplugged = 2
+ limit = 64
+ numa = false
+ sockets = 2
+ type = "host"
+ units = 1024
+ flags = ["+aes"]
+ }
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ "cpu.cores": "2",
+ "cpu.hotplugged": "2",
+ "cpu.limit": "64",
+ "cpu.numa": "false",
+ "cpu.sockets": "2",
+ "cpu.type": "host",
+ "cpu.units": "1024",
+ }),
+ ),
+ },
+ { // now update the cpu params and check if they are updated
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ cpu = {
+ cores = 4
+ hotplugged = 2
+ limit = null # setting to null is the same as removal
+ # numa = false
+ # sockets = 2 remove sockets, so it should fall back to 1 (PVE default)
+ # type = "host" remove type, so it should fall back to kvm64 (PVE default)
+ units = 2048
+ # flags = ["+aes"]
+ }
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ "cpu.cores": "4",
+ "cpu.hotplugged": "2",
+ "cpu.sockets": "1", // default value, but it is a special case.
+ "cpu.type": "kvm64", // default value, but it is a special case.
+ "cpu.units": "2048",
+ }),
+ testNoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
+ "cpu.limit", // other defaults are not set in the state
+ "cpu.numa",
+ "cpu.flags",
+ }),
+ ),
+ },
+ {
+ RefreshState: true,
+ },
+ }},
+ {"clone VM with some cpu params", []resource.TestStep{{
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "template_vm" {
+ node_name = "{{.NodeName}}"
+ name = "template-cpu"
+ cpu = {
+ cores = 2
+ sockets = 2
+ type = "host"
+ }
+ }
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ clone = {
+ id = proxmox_virtual_environment_vm2.template_vm.id
+ }
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ "cpu.cores": "2",
+ "cpu.sockets": "2",
+ "cpu.type": "host",
+ }),
+ ),
+ }}},
+ {"clone VM with some cpu params and updating them in the clone", []resource.TestStep{{
+ Config: te.renderConfig(`
+ resource "proxmox_virtual_environment_vm2" "template_vm" {
+ node_name = "{{.NodeName}}"
+ name = "template-cpu"
+ cpu = {
+ cores = 2
+ sockets = 2
+ type = "host"
+ }
+ }
+ resource "proxmox_virtual_environment_vm2" "test_vm" {
+ node_name = "{{.NodeName}}"
+ name = "test-cpu"
+ clone = {
+ id = proxmox_virtual_environment_vm2.template_vm.id
+ }
+ cpu = {
+ cores = 4
+ units = 1024
+ }
+ }`),
+ Check: resource.ComposeTestCheckFunc(
+ testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
+ "cpu.cores": "4",
+ "cpu.sockets": "2",
+ "cpu.type": "host",
+ "cpu.units": "1024",
+ }),
+ ),
+ }}},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resource.ParallelTest(t, resource.TestCase{
+ ProtoV6ProviderFactories: te.accProviders,
+ Steps: tt.steps,
+ })
+ })
+ }
+}
+
func TestAccResourceVM2Clone(t *testing.T) {
t.Parallel()
diff --git a/fwprovider/types/tags/attribute.go b/fwprovider/types/stringset/attribute.go
similarity index 71%
rename from fwprovider/types/tags/attribute.go
rename to fwprovider/types/stringset/attribute.go
index 1fe06183..e5009ee7 100644
--- a/fwprovider/types/tags/attribute.go
+++ b/fwprovider/types/stringset/attribute.go
@@ -1,4 +1,4 @@
-package tags
+package stringset
import (
"regexp"
@@ -10,18 +10,19 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-// ResourceAttribute returns a resource schema attribute for tags.
-func ResourceAttribute() schema.SetAttribute {
+// ResourceAttribute returns a resource schema attribute for string set.
+func ResourceAttribute(desc, markdownDesc string) schema.SetAttribute {
return schema.SetAttribute{
CustomType: Type{
SetType: types.SetType{
ElemType: types.StringType,
},
},
- Description: "The tags assigned to the resource.",
- Optional: true,
- Computed: true,
- ElementType: types.StringType,
+ Description: desc,
+ MarkdownDescription: markdownDesc,
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
Validators: []validator.Set{
// NOTE: we allow empty list to remove all previously set tags
setvalidator.ValueStringsAre(
diff --git a/fwprovider/types/tags/type.go b/fwprovider/types/stringset/type.go
similarity index 83%
rename from fwprovider/types/tags/type.go
rename to fwprovider/types/stringset/type.go
index fdf1a3d1..53b9306b 100644
--- a/fwprovider/types/tags/type.go
+++ b/fwprovider/types/stringset/type.go
@@ -1,4 +1,4 @@
-package tags
+package stringset
import (
"context"
@@ -15,7 +15,7 @@ var (
_ basetypes.SetTypable = Type{}
)
-// Type defines the type for tags.
+// Type defines the type for string set.
type Type struct {
basetypes.SetType
}
@@ -33,7 +33,7 @@ func (t Type) Equal(o attr.Type) bool {
// String returns a string representation of the type.
func (t Type) String() string {
- return "TagsType"
+ return "StringSetType"
}
// ValueFromSet converts the set value to a SetValuable type.
@@ -45,11 +45,11 @@ func (t Type) ValueFromSet(_ context.Context, in basetypes.SetValue) (basetypes.
return value, nil
}
-// ValueFromTerraform converts the Terraform value to a SetValue type.
+// ValueFromTerraform converts the Terraform value to a NewValue type.
func (t Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.SetType.ValueFromTerraform(ctx, in)
if err != nil {
- return nil, fmt.Errorf("error converting Terraform value to SetValue")
+ return nil, fmt.Errorf("error converting Terraform value to NewValue")
}
setValue, ok := attrValue.(basetypes.SetValue)
@@ -59,7 +59,7 @@ func (t Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Va
setValuable, diags := t.ValueFromSet(ctx, setValue)
if diags.HasError() {
- return nil, fmt.Errorf("error converting SetValue to SetValuable: %v", diags)
+ return nil, fmt.Errorf("error converting NewValue to SetValuable: %v", diags)
}
return setValuable, nil
diff --git a/fwprovider/types/tags/value.go b/fwprovider/types/stringset/value.go
similarity index 68%
rename from fwprovider/types/tags/value.go
rename to fwprovider/types/stringset/value.go
index c5181807..f37791ee 100644
--- a/fwprovider/types/tags/value.go
+++ b/fwprovider/types/stringset/value.go
@@ -1,4 +1,4 @@
-package tags
+package stringset
import (
"context"
@@ -17,7 +17,7 @@ var (
_ basetypes.SetValuable = Value{}
)
-// Value defines the value for tags.
+// Value defines the value for string set.
type Value struct {
basetypes.SetValue
}
@@ -40,7 +40,7 @@ func (v Value) Equal(o attr.Value) bool {
return v.SetValue.Equal(other.SetValue)
}
-// ValueStringPointer returns a pointer to the string representation of tags set value.
+// ValueStringPointer returns a pointer to the string representation of string set value.
func (v Value) ValueStringPointer(ctx context.Context, diags *diag.Diagnostics) *string {
if v.IsNull() || v.IsUnknown() || len(v.Elements()) == 0 {
return nil
@@ -54,33 +54,33 @@ func (v Value) ValueStringPointer(ctx context.Context, diags *diag.Diagnostics)
return nil
}
- var sanitizedTags []string
+ var sanitizedItems []string
for _, el := range elems {
if el.IsNull() || el.IsUnknown() {
continue
}
- sanitizedTag := strings.TrimSpace(el.ValueString())
- if len(sanitizedTag) > 0 {
- sanitizedTags = append(sanitizedTags, sanitizedTag)
+ sanitizedItem := strings.TrimSpace(el.ValueString())
+ if len(sanitizedItem) > 0 {
+ sanitizedItems = append(sanitizedItems, sanitizedItem)
}
}
- return ptr.Ptr(strings.Join(sanitizedTags, ";"))
+ return ptr.Ptr(strings.Join(sanitizedItems, ";"))
}
-// SetValue converts a string of tags to a tags set value.
-func SetValue(tagsStr *string, diags *diag.Diagnostics) Value {
- if tagsStr == nil {
- return Value{types.SetNull(types.StringType)}
+// NewValue converts a string of items to a new string set value.
+func NewValue(str *string, diags *diag.Diagnostics) Value {
+ if str == nil {
+ return Value{types.SetValueMust(types.StringType, []attr.Value{})}
}
- tags := strings.Split(*tagsStr, ";")
- elems := make([]attr.Value, len(tags))
+ items := strings.Split(*str, ";")
+ elems := make([]attr.Value, len(items))
- for i, tag := range tags {
- elems[i] = types.StringValue(tag)
+ for i, item := range items {
+ elems[i] = types.StringValue(item)
}
setValue, d := types.SetValue(types.StringType, elems)
diff --git a/fwprovider/vm/cpu/resource_cpu.go b/fwprovider/vm/cpu/resource_cpu.go
new file mode 100644
index 00000000..6624c98f
--- /dev/null
+++ b/fwprovider/vm/cpu/resource_cpu.go
@@ -0,0 +1,347 @@
+package cpu
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-go/tftypes"
+
+ "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
+ proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
+)
+
+var (
+ _ basetypes.ObjectTypable = Type{}
+ _ basetypes.ObjectValuable = Value{}
+)
+
+// Type is an attribute type that represents CPU settings.
+type Type struct {
+ basetypes.ObjectType
+}
+
+// String returns a human-readable representation of the type.
+func (t Type) String() string {
+ return "cpu.Type"
+}
+
+// ValueFromObject returns a Value given a basetypes.ObjectValue.
+func (t Type) ValueFromObject(
+ _ context.Context,
+ in basetypes.ObjectValue,
+) (basetypes.ObjectValuable, diag.Diagnostics) {
+ value := Value{
+ Object: in,
+ }
+
+ return value, nil
+}
+
+// ValueFromTerraform returns a Value given a tftypes.Value.
+// Value embeds the types.Object value returned from calling ValueFromTerraform on the
+// types.ObjectType embedded in Type.
+func (t Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
+ val, err := t.ObjectType.ValueFromTerraform(ctx, in)
+ if err != nil {
+ return nil, fmt.Errorf("failed to convert value to types.Object: %w", err)
+ }
+
+ obj, ok := val.(types.Object)
+ if !ok {
+ return nil, fmt.Errorf("%T cannot be used as types.Object", val)
+ }
+
+ return Value{obj}, nil
+}
+
+// ValueType returns the associated Value type for debugging.
+func (t Type) ValueType(context.Context) attr.Value {
+ // It does not need to be a fully valid implementation of the type.
+ return Value{}
+}
+
+// Equal returns true if `candidate` is also a Type and has the same
+// AttributeTypes.
+func (t Type) Equal(candidate attr.Type) bool {
+ other, ok := candidate.(Type)
+ if !ok {
+ return false
+ }
+
+ return t.ObjectType.Equal(other.ObjectType)
+}
+
+// Value represents an object containing values to be used as CPU settings.
+type Value struct {
+ types.Object
+}
+
+// Equal returns true if the Value is considered semantically equal
+// (same type and same value) to the attr.Value passed as an argument.
+func (v Value) Equal(c attr.Value) bool {
+ other, ok := c.(Value)
+
+ if !ok {
+ return false
+ }
+
+ return v.Object.Equal(other.Object)
+}
+
+// ToObjectValue returns the underlying ObjectValue.
+func (v Value) ToObjectValue(_ context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
+ return v.Object, nil
+}
+
+// Type returns a Type with the same attribute types as `t`.
+func (v Value) Type(ctx context.Context) attr.Type {
+ return Type{
+ types.ObjectType{
+ AttrTypes: v.AttributeTypes(ctx),
+ },
+ }
+}
+
+// NewValue returns a new Value with the given CPU settings from the PVE API.
+func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value {
+ cpu := Model{}
+
+ cpu.Affinity = types.StringPointerValue(config.CPUAffinity)
+ cpu.Architecture = types.StringPointerValue(config.CPUArchitecture)
+ cpu.Hotplugged = types.Int64PointerValue(config.VirtualCPUCount)
+ cpu.Limit = types.Int64PointerValue(config.CPULimit.PointerInt64())
+ cpu.Numa = types.BoolPointerValue(config.NUMAEnabled.PointerBool())
+ cpu.Units = types.Int64PointerValue(config.CPUUnits)
+
+ // special cases: PVE does not return actual value for cores VM, etc is using default (i.e. a value is not specified)
+
+ if config.CPUCores != nil {
+ cpu.Cores = types.Int64PointerValue(config.CPUCores)
+ } else {
+ cpu.Cores = types.Int64Value(1)
+ }
+
+ if config.CPUSockets != nil {
+ cpu.Sockets = types.Int64PointerValue(config.CPUSockets)
+ } else {
+ cpu.Sockets = types.Int64Value(1)
+ }
+
+ if config.CPUEmulation != nil {
+ cpu.Type = types.StringValue(config.CPUEmulation.Type)
+
+ flags, d := types.SetValueFrom(ctx, basetypes.StringType{}, config.CPUEmulation.Flags)
+ diags.Append(d...)
+
+ cpu.Flags = flags
+ } else {
+ cpu.Type = types.StringValue("kvm64")
+ cpu.Flags = types.SetNull(basetypes.StringType{})
+ }
+
+ obj, d := types.ObjectValueFrom(ctx, attributeTypes(), cpu)
+ diags.Append(d...)
+
+ return Value{obj}
+}
+
+// FillCreateBody fills the CreateRequestBody with the CPU settings from the Value.
+//
+// In the 'create' context, v is the plan.
+func (v Value) FillCreateBody(ctx context.Context, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
+ var plan Model
+
+ if v.IsNull() || v.IsUnknown() {
+ return
+ }
+
+ d := v.Object.As(ctx, &plan, basetypes.ObjectAsOptions{})
+ diags.Append(d...)
+
+ if d.HasError() {
+ return
+ }
+
+ // for computed fields, we need to check if they are unknown
+ if !plan.Affinity.IsUnknown() {
+ body.CPUAffinity = plan.Affinity.ValueStringPointer()
+ }
+
+ if !plan.Architecture.IsUnknown() {
+ body.CPUArchitecture = plan.Architecture.ValueStringPointer()
+ }
+
+ if !plan.Cores.IsUnknown() {
+ body.CPUCores = plan.Cores.ValueInt64Pointer()
+ }
+
+ if !plan.Limit.IsUnknown() {
+ body.CPULimit = plan.Limit.ValueInt64Pointer()
+ }
+
+ if !plan.Sockets.IsUnknown() {
+ body.CPUSockets = plan.Sockets.ValueInt64Pointer()
+ }
+
+ if !plan.Units.IsUnknown() {
+ body.CPUUnits = plan.Units.ValueInt64Pointer()
+ }
+
+ if !plan.Numa.IsUnknown() {
+ body.NUMAEnabled = proxmoxtypes.CustomBoolPtr(plan.Numa.ValueBoolPointer())
+ }
+
+ if !plan.Hotplugged.IsUnknown() {
+ body.VirtualCPUCount = plan.Hotplugged.ValueInt64Pointer()
+ }
+
+ body.CPUEmulation = &vms.CustomCPUEmulation{}
+
+ if !plan.Type.IsUnknown() {
+ body.CPUEmulation.Type = plan.Type.ValueString()
+ }
+
+ if !plan.Flags.IsUnknown() {
+ d = plan.Flags.ElementsAs(ctx, &body.CPUEmulation.Flags, false)
+ diags.Append(d...)
+ }
+}
+
+// FillUpdateBody fills the UpdateRequestBody with the CPU settings from the Value.
+//
+// In the 'update' context, v is the plan and stateValue is the current state.
+func (v Value) FillUpdateBody(
+ ctx context.Context,
+ stateValue Value,
+ updateBody *vms.UpdateRequestBody,
+ isClone bool,
+ diags *diag.Diagnostics,
+) {
+ var plan, state Model
+
+ if v.IsNull() || v.IsUnknown() || v.Equal(stateValue) {
+ return
+ }
+
+ d := v.Object.As(ctx, &plan, basetypes.ObjectAsOptions{})
+ diags.Append(d...)
+ d = stateValue.Object.As(ctx, &state, basetypes.ObjectAsOptions{})
+ diags.Append(d...)
+
+ if diags.HasError() {
+ return
+ }
+
+ var errs []error
+
+ del := func(field string) {
+ errs = append(errs, updateBody.ToDelete(field))
+ }
+
+ if !plan.Affinity.Equal(state.Affinity) {
+ if shouldBeRemoved(plan.Affinity, state.Affinity, isClone) {
+ del("CPUAffinity")
+ } else if isDefined(plan.Affinity) {
+ updateBody.CPUAffinity = plan.Affinity.ValueStringPointer()
+ }
+ }
+
+ if !plan.Architecture.Equal(state.Architecture) {
+ if shouldBeRemoved(plan.Architecture, state.Architecture, isClone) {
+ del("CPUArchitecture")
+ } else if isDefined(plan.Architecture) {
+ updateBody.CPUArchitecture = plan.Architecture.ValueStringPointer()
+ }
+ }
+
+ if !plan.Cores.Equal(state.Cores) {
+ if shouldBeRemoved(plan.Cores, state.Cores, isClone) {
+ del("CPUCores")
+ } else if isDefined(plan.Cores) {
+ updateBody.CPUCores = plan.Cores.ValueInt64Pointer()
+ }
+ }
+
+ if !plan.Limit.Equal(state.Limit) {
+ if shouldBeRemoved(plan.Limit, state.Limit, isClone) {
+ del("CPULimit")
+ } else if isDefined(plan.Sockets) {
+ updateBody.CPULimit = plan.Limit.ValueInt64Pointer()
+ }
+ }
+
+ if !plan.Sockets.Equal(state.Sockets) {
+ if shouldBeRemoved(plan.Sockets, state.Sockets, isClone) {
+ del("CPUSockets")
+ } else if isDefined(plan.Sockets) {
+ updateBody.CPUSockets = plan.Sockets.ValueInt64Pointer()
+ }
+ }
+
+ if !plan.Units.Equal(state.Units) {
+ if shouldBeRemoved(plan.Units, state.Units, isClone) {
+ del("CPUUnits")
+ } else if isDefined(plan.Units) {
+ updateBody.CPUUnits = plan.Units.ValueInt64Pointer()
+ }
+ }
+
+ if !plan.Numa.Equal(state.Numa) {
+ if shouldBeRemoved(plan.Numa, state.Numa, isClone) {
+ del("NUMAEnabled")
+ } else if isDefined(plan.Numa) {
+ updateBody.NUMAEnabled = proxmoxtypes.CustomBoolPtr(plan.Numa.ValueBoolPointer())
+ }
+ }
+
+ if !plan.Hotplugged.Equal(state.Hotplugged) {
+ if shouldBeRemoved(plan.Hotplugged, state.Hotplugged, isClone) {
+ del("VirtualCPUCount")
+ } else if isDefined(plan.Hotplugged) {
+ updateBody.VirtualCPUCount = plan.Hotplugged.ValueInt64Pointer()
+ }
+ }
+
+ var delType, delFlags bool
+
+ cpuEmulation := &vms.CustomCPUEmulation{}
+
+ if !plan.Type.Equal(state.Type) {
+ if shouldBeRemoved(plan.Type, state.Type, isClone) {
+ delType = true
+ } else if isDefined(plan.Type) {
+ cpuEmulation.Type = plan.Type.ValueString()
+ }
+ }
+
+ if !plan.Flags.Equal(state.Flags) {
+ if shouldBeRemoved(plan.Flags, state.Flags, isClone) {
+ delFlags = true
+ } else if isDefined(plan.Flags) {
+ d = plan.Flags.ElementsAs(ctx, &cpuEmulation.Flags, false)
+ diags.Append(d...)
+ }
+ }
+
+ switch {
+ case delType && !delFlags:
+ diags.AddError("Cannot have CPU flags without explicit definition of CPU type", "")
+ case delType:
+ del("CPUEmulation")
+ case !reflect.DeepEqual(cpuEmulation, &vms.CustomCPUEmulation{}):
+ updateBody.CPUEmulation = cpuEmulation
+ }
+}
+
+func shouldBeRemoved(plan attr.Value, state attr.Value, isClone bool) bool {
+ return !isDefined(plan) && isDefined(state) && !isClone
+}
+
+func isDefined(v attr.Value) bool {
+ return !v.IsNull() && !v.IsUnknown()
+}
diff --git a/fwprovider/vm/cpu/resource_cpu_model.go b/fwprovider/vm/cpu/resource_cpu_model.go
new file mode 100644
index 00000000..62490e3e
--- /dev/null
+++ b/fwprovider/vm/cpu/resource_cpu_model.go
@@ -0,0 +1,35 @@
+package cpu
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Model represents the CPU model.
+type Model struct {
+ Affinity types.String `tfsdk:"affinity"`
+ Architecture types.String `tfsdk:"architecture"`
+ Cores types.Int64 `tfsdk:"cores"`
+ Flags types.Set `tfsdk:"flags"`
+ Hotplugged types.Int64 `tfsdk:"hotplugged"`
+ Limit types.Int64 `tfsdk:"limit"`
+ Numa types.Bool `tfsdk:"numa"`
+ Sockets types.Int64 `tfsdk:"sockets"`
+ Type types.String `tfsdk:"type"`
+ Units types.Int64 `tfsdk:"units"`
+}
+
+func attributeTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "affinity": types.StringType,
+ "architecture": types.StringType,
+ "cores": types.Int64Type,
+ "flags": types.SetType{ElemType: types.StringType},
+ "hotplugged": types.Int64Type,
+ "limit": types.Int64Type,
+ "numa": types.BoolType,
+ "sockets": types.Int64Type,
+ "type": types.StringType,
+ "units": types.Int64Type,
+ }
+}
diff --git a/fwprovider/vm/cpu/resource_cpu_schema.go b/fwprovider/vm/cpu/resource_cpu_schema.go
new file mode 100644
index 00000000..104c2922
--- /dev/null
+++ b/fwprovider/vm/cpu/resource_cpu_schema.go
@@ -0,0 +1,218 @@
+package cpu
+
+import (
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
+ "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"
+)
+
+// Schema defines the schema for the CPU resource.
+func Schema() schema.Attribute {
+ return schema.SingleNestedAttribute{
+ CustomType: Type{
+ ObjectType: 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'",
+ MarkdownDescription: "The CPU cores that are used to run the VM’s vCPU. The value is a list of CPU IDs, " +
+ "separated by commas. The CPU IDs are zero-based. For example, `0,1,2,3` " +
+ "(which also can be shortened to `0-3`) means that the VM’s vCPUs are run on the first " +
+ "four CPU cores. Setting `affinity` is only allowed for `root@pam` authenticated user.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^\d+[\d-,]*$`),
+ "must contain numbers or number ranges separated by ','"),
+ },
+ },
+ "architecture": schema.StringAttribute{
+ Description: "The CPU architecture.",
+ MarkdownDescription: "The CPU architecture `` (defaults to the host). " +
+ "Setting `affinity` is only allowed for `root@pam` authenticated user.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("aarch64", "x86_64"),
+ },
+ },
+ "cores": schema.Int64Attribute{
+ Description: "The number of CPU cores per socket.",
+ MarkdownDescription: "The number of CPU cores per socket (defaults to `1`).",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Int64{
+ int64validator.Between(1, 1024),
+ },
+ },
+ "flags": schema.SetAttribute{
+ Description: "Set of additional CPU flags.",
+ MarkdownDescription: "Set of additional CPU flags. " +
+ "Use `+FLAG` to enable, `-FLAG` to disable a flag. Custom CPU models can specify any flag " +
+ "supported by QEMU/KVM, VM-specific flags must be from the following set for security reasons: " +
+ "`pcid`, `spec-ctrl`, `ibpb`, `ssbd`, `virt-ssbd`, `amd-ssbd`, `amd-no-ssb`, `pdpe1gb`, " +
+ "`md-clear`, `hv-tlbflush`, `hv-evmcs`, `aes`.",
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
+ Validators: []validator.Set{
+ setvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("type")),
+ setvalidator.ValueStringsAre(
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`(.|\s)*\S(.|\s)*`),
+ "must be a non-empty and non-whitespace string",
+ ),
+ stringvalidator.LengthAtLeast(1),
+ ),
+ },
+ },
+ "hotplugged": schema.Int64Attribute{
+ Description: "The number of hotplugged vCPUs.",
+ MarkdownDescription: "The number of hotplugged vCPUs (defaults to `0`).",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Int64{
+ int64validator.Between(1, 1024),
+ },
+ },
+ "limit": schema.Int64Attribute{
+ Description: "Limit of CPU usage.",
+ MarkdownDescription: "Limit of CPU usage (defaults to `0` which means no limit).",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Int64{
+ int64validator.Between(1, 128),
+ },
+ },
+ "numa": schema.BoolAttribute{
+ Description: "Enable NUMA.",
+ MarkdownDescription: "Enable NUMA (defaults to `false`).",
+ Optional: true,
+ Computed: true,
+ },
+ "sockets": schema.Int64Attribute{
+ Description: "The number of CPU sockets.",
+ MarkdownDescription: "The number of CPU sockets (defaults to `1`).",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Int64{
+ int64validator.Between(1, 16),
+ },
+ },
+ "type": schema.StringAttribute{
+ Description: "Emulated CPU type.",
+ MarkdownDescription: "Emulated CPU type, " +
+ "it's recommended to use `x86-64-v2-AES` or higher (defaults to `kvm64`). " +
+ "See https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings " +
+ "for more information.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ "486",
+ "Broadwell",
+ "Broadwell-IBRS",
+ "Broadwell-noTSX",
+ "Broadwell-noTSX-IBRS",
+ "Cascadelake-Server",
+ "Cascadelake-Server-noTSX",
+ "Cascadelake-Server-v2",
+ "Cascadelake-Server-v4",
+ "Cascadelake-Server-v5",
+ "Conroe",
+ "Cooperlake",
+ "Cooperlake-v2",
+ "EPYC",
+ "EPYC-IBPB",
+ "EPYC-Milan",
+ "EPYC-Rome",
+ "EPYC-Rome-v2",
+ "EPYC-v3",
+ "Haswell",
+ "Haswell-IBRS",
+ "Haswell-noTSX",
+ "Haswell-noTSX-IBRS",
+ "Icelake-Client",
+ "Icelake-Client-noTSX",
+ "Icelake-Server",
+ "Icelake-Server-noTSX",
+ "Icelake-Server-v3",
+ "Icelake-Server-v4",
+ "Icelake-Server-v5",
+ "Icelake-Server-v6",
+ "IvyBridge",
+ "IvyBridge-IBRS",
+ "KnightsMill",
+ "Nehalem",
+ "Nehalem-IBRS",
+ "Opteron_G1",
+ "Opteron_G2",
+ "Opteron_G3",
+ "Opteron_G4",
+ "Opteron_G5",
+ "Penryn",
+ "SandyBridge",
+ "SandyBridge-IBRS",
+ "SapphireRapids",
+ "Skylake-Client",
+ "Skylake-Client-IBRS",
+ "Skylake-Client-noTSX-IBRS",
+ "Skylake-Client-v4",
+ "Skylake-Server",
+ "Skylake-Server-IBRS",
+ "Skylake-Server-noTSX-IBRS",
+ "Skylake-Server-v4",
+ "Skylake-Server-v5",
+ "Westmere",
+ "Westmere-IBRS",
+ "athlon",
+ "core2duo",
+ "coreduo",
+ "host",
+ "kvm32",
+ "kvm64",
+ "max",
+ "pentium",
+ "pentium2",
+ "pentium3",
+ "phenom",
+ "qemu32",
+ "qemu64",
+ "x86-64-v2",
+ "x86-64-v2-AES",
+ "x86-64-v3",
+ "x86-64-v4",
+ ),
+ },
+ },
+ "units": schema.Int64Attribute{
+ Description: "CPU weight for a VM. Argument is used in the kernel fair scheduler. " +
+ "The larger the number is, the more CPU time this VM gets. " +
+ "Number is relative to weights of all the other running VMs.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.Int64{
+ int64validator.Between(2, 262144),
+ },
+ },
+ },
+ }
+}
diff --git a/fwprovider/vm/resource_model.go b/fwprovider/vm/resource_model.go
deleted file mode 100644
index 6b9d8899..00000000
--- a/fwprovider/vm/resource_model.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package vm
-
-import (
- "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
- "github.com/hashicorp/terraform-plugin-framework/types"
-
- "github.com/bpg/terraform-provider-proxmox/fwprovider/types/tags"
-)
-
-type vmModel struct {
- Description types.String `tfsdk:"description"`
- Clone *struct {
- ID types.Int64 `tfsdk:"id"`
- Retries types.Int64 `tfsdk:"retries"`
- } `tfsdk:"clone"`
- ID types.Int64 `tfsdk:"id"`
- Name types.String `tfsdk:"name"`
- NodeName types.String `tfsdk:"node_name"`
- Tags tags.Value `tfsdk:"tags"`
- Template types.Bool `tfsdk:"template"`
- Timeouts timeouts.Value `tfsdk:"timeouts"`
-}
diff --git a/fwprovider/vm/resource.go b/fwprovider/vm/resource_vm.go
similarity index 80%
rename from fwprovider/vm/resource.go
rename to fwprovider/vm/resource_vm.go
index 8ffa3264..f8e0fe37 100644
--- a/fwprovider/vm/resource.go
+++ b/fwprovider/vm/resource_vm.go
@@ -15,7 +15,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
- "github.com/bpg/terraform-provider-proxmox/fwprovider/types/tags"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
@@ -33,22 +34,23 @@ const (
)
var (
- _ resource.Resource = &vmResource{}
- _ resource.ResourceWithConfigure = &vmResource{}
- _ resource.ResourceWithImportState = &vmResource{}
+ _ resource.Resource = &Resource{}
+ _ resource.ResourceWithConfigure = &Resource{}
+ _ resource.ResourceWithImportState = &Resource{}
)
-type vmResource struct {
+// Resource implements the resource.Resource interface for managing VMs.
+type Resource struct {
client proxmox.Client
}
// NewVMResource creates a new resource for managing VMs.
func NewVMResource() resource.Resource {
- return &vmResource{}
+ return &Resource{}
}
// Metadata defines the name of the resource.
-func (r *vmResource) Metadata(
+func (r *Resource) Metadata(
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
@@ -56,7 +58,8 @@ func (r *vmResource) Metadata(
resp.TypeName = req.ProviderTypeName + "_vm2"
}
-func (r *vmResource) Configure(
+// Configure sets the client for the resource.
+func (r *Resource) Configure(
_ context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
@@ -79,8 +82,9 @@ func (r *vmResource) Configure(
r.client = client
}
-func (r *vmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var plan vmModel
+// Create creates a new VM.
+func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
@@ -132,7 +136,7 @@ func (r *vmResource) Create(ctx context.Context, req resource.CreateRequest, res
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}
-func (r *vmResource) create(ctx context.Context, plan vmModel, diags *diag.Diagnostics) {
+func (r *Resource) create(ctx context.Context, plan Model, diags *diag.Diagnostics) {
createBody := &vms.CreateRequestBody{
Description: plan.Description.ValueStringPointer(),
Name: plan.Name.ValueStringPointer(),
@@ -141,6 +145,9 @@ func (r *vmResource) create(ctx context.Context, plan vmModel, diags *diag.Diagn
VMID: int(plan.ID.ValueInt64()),
}
+ // fill out create body fields with values from other resource blocks
+ plan.CPU.FillCreateBody(ctx, createBody, diags)
+
if diags.HasError() {
return
}
@@ -154,7 +161,7 @@ func (r *vmResource) create(ctx context.Context, plan vmModel, diags *diag.Diagn
}
}
-func (r *vmResource) clone(ctx context.Context, plan vmModel, diags *diag.Diagnostics) {
+func (r *Resource) clone(ctx context.Context, plan Model, diags *diag.Diagnostics) {
if plan.Clone == nil {
diags.AddError("Clone configuration is missing", "")
return
@@ -180,8 +187,9 @@ func (r *vmResource) clone(ctx context.Context, plan vmModel, diags *diag.Diagno
}
// now load the clone's configuration into a temporary model and update what is needed comparing to the plan
- clone := vmModel{
+ clone := Model{
ID: plan.ID,
+ CPU: plan.CPU,
Name: plan.Name,
Description: plan.Description,
NodeName: plan.NodeName,
@@ -193,11 +201,11 @@ func (r *vmResource) clone(ctx context.Context, plan vmModel, diags *diag.Diagno
return
}
- r.update(ctx, plan, clone, diags)
+ r.update(ctx, plan, clone, true, diags)
}
-func (r *vmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var state vmModel
+func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state Model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
@@ -230,8 +238,9 @@ func (r *vmResource) Read(ctx context.Context, req resource.ReadRequest, resp *r
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
-func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var plan, state vmModel
+// Update updates the VM with the new configuration.
+func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan, state Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
@@ -246,7 +255,7 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
- r.update(ctx, plan, state, &resp.Diagnostics)
+ r.update(ctx, plan, state, false, &resp.Diagnostics)
// read back the VM from the PVE API to populate computed fields
exists := r.read(ctx, &plan, &resp.Diagnostics)
@@ -262,12 +271,18 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}
-func (r *vmResource) update(ctx context.Context, plan, state vmModel, diags *diag.Diagnostics) {
+// update updates the VM with the new configuration.
+//
+// The isClone parameter is used to determine if the VM is being updated as part of a clone operation.
+// During a clone operation, the attributes are copied from the source VM to the clone, so for computed attributes
+// that are optional, we need to handle them differently. If they are not set in the clone configuration, we keep the
+// source VM's values.
+// During the normal update operation, if a computed attribute is not set in the plan, we remove it from the VM, so it
+// can assume its default PVE-provided value.
+func (r *Resource) update(ctx context.Context, plan, state Model, isClone bool, diags *diag.Diagnostics) {
vmAPI := r.client.Node(plan.NodeName.ValueString()).VM(int(plan.ID.ValueInt64()))
- updateBody := &vms.UpdateRequestBody{
- VMID: int(plan.ID.ValueInt64()),
- }
+ updateBody := &vms.UpdateRequestBody{}
var errs []error
@@ -305,15 +320,22 @@ func (r *vmResource) update(ctx context.Context, plan, state vmModel, diags *dia
}
}
- err := vmAPI.UpdateVM(ctx, updateBody)
- if err != nil {
- diags.AddError("Failed to update VM", err.Error())
- return
+ plan.CPU.FillUpdateBody(ctx, state.CPU, updateBody, isClone, diags)
+
+ if !updateBody.IsEmpty() {
+ updateBody.VMID = int(plan.ID.ValueInt64())
+
+ err := vmAPI.UpdateVM(ctx, updateBody)
+ if err != nil {
+ diags.AddError("Failed to update VM", err.Error())
+ return
+ }
}
}
-func (r *vmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var state vmModel
+// Delete deletes the VM.
+func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state Model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
@@ -366,7 +388,8 @@ func (r *vmResource) Delete(ctx context.Context, req resource.DeleteRequest, res
resp.State.RemoveResource(ctx)
}
-func (r *vmResource) ImportState(
+// ImportState imports the state of the VM from the API.
+func (r *Resource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
@@ -394,7 +417,7 @@ func (r *vmResource) ImportState(
return
}
- state := vmModel{
+ state := Model{
ID: types.Int64Value(int64(id)),
NodeName: types.StringValue(nodeName),
Timeouts: ts,
@@ -415,7 +438,7 @@ func (r *vmResource) ImportState(
// read retrieves the current state of the resource from the API and updates the state.
// Returns false if the resource does not exist, so the caller can remove it from the state if necessary.
-func (r *vmResource) read(ctx context.Context, model *vmModel, diags *diag.Diagnostics) bool {
+func (r *Resource) read(ctx context.Context, model *Model, diags *diag.Diagnostics) bool {
vmAPI := r.client.Node(model.NodeName.ValueString()).VM(int(model.ID.ValueInt64()))
// Retrieve the entire configuration in order to compare it to the state.
@@ -448,11 +471,8 @@ func (r *vmResource) read(ctx context.Context, model *vmModel, diags *diag.Diagn
// Optional fields can be removed from the model, use StringPointerValue to handle removal on nil
model.Description = types.StringPointerValue(config.Description)
model.Name = types.StringPointerValue(config.Name)
-
- if model.Tags.IsNull() || model.Tags.IsUnknown() { // only for computed
- model.Tags = tags.SetValue(config.Tags, diags)
- }
-
+ model.CPU = cpu.NewValue(ctx, config, diags)
+ model.Tags = stringset.NewValue(config.Tags, diags)
model.Template = types.BoolPointerValue(config.Template.PointerBool())
return true
diff --git a/fwprovider/vm/resource_vm_model.go b/fwprovider/vm/resource_vm_model.go
new file mode 100644
index 00000000..c31bdd95
--- /dev/null
+++ b/fwprovider/vm/resource_vm_model.go
@@ -0,0 +1,26 @@
+package vm
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
+)
+
+// Model represents the VM model.
+type Model struct {
+ Description types.String `tfsdk:"description"`
+ // for computed fields / blocks we have to use custom type? (because of unknown?)
+ CPU cpu.Value `tfsdk:"cpu"`
+ Clone *struct {
+ ID types.Int64 `tfsdk:"id"`
+ Retries types.Int64 `tfsdk:"retries"`
+ } `tfsdk:"clone"`
+ ID types.Int64 `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ NodeName types.String `tfsdk:"node_name"`
+ Tags stringset.Value `tfsdk:"tags"`
+ Template types.Bool `tfsdk:"template"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
diff --git a/fwprovider/vm/resource_schema.go b/fwprovider/vm/resource_vm_schema.go
similarity index 92%
rename from fwprovider/vm/resource_schema.go
rename to fwprovider/vm/resource_vm_schema.go
index cbbf1d4f..79902b15 100644
--- a/fwprovider/vm/resource_schema.go
+++ b/fwprovider/vm/resource_vm_schema.go
@@ -15,11 +15,12 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
- "github.com/bpg/terraform-provider-proxmox/fwprovider/types/tags"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
)
// Schema defines the schema for the resource.
-func (r *vmResource) Schema(
+func (r *Resource) Schema(
ctx context.Context,
_ resource.SchemaRequest,
resp *resource.SchemaResponse,
@@ -49,6 +50,7 @@ func (r *vmResource) Schema(
},
},
},
+ "cpu": cpu.Schema(),
"description": schema.StringAttribute{
Description: "The description of the VM.",
Optional: true,
@@ -77,7 +79,7 @@ func (r *vmResource) Schema(
Description: "The name of the node where the VM is provisioned.",
Required: true,
},
- "tags": tags.ResourceAttribute(),
+ "tags": stringset.ResourceAttribute("The tags assigned to the resource.", ""),
"template": schema.BoolAttribute{
Description: "Set to true to create a VM template.",
Optional: true,
diff --git a/proxmox/nodes/vms/vms_types.go b/proxmox/nodes/vms/vms_types.go
index fc9d6b81..46747203 100644
--- a/proxmox/nodes/vms/vms_types.go
+++ b/proxmox/nodes/vms/vms_types.go
@@ -234,11 +234,11 @@ type CreateRequestBody struct {
CDROM *string `json:"cdrom,omitempty" url:"cdrom,omitempty"`
CloudInitConfig *CustomCloudInitConfig `json:"cloudinit,omitempty" url:"cloudinit,omitempty"`
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
- CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
+ CPUCores *int64 `json:"cores,omitempty" url:"cores,omitempty"`
CPUEmulation *CustomCPUEmulation `json:"cpu,omitempty" url:"cpu,omitempty"`
- CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
- CPUSockets *int `json:"sockets,omitempty" url:"sockets,omitempty"`
- CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
+ CPULimit *int64 `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
+ CPUSockets *int64 `json:"sockets,omitempty" url:"sockets,omitempty"`
+ CPUUnits *int64 `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
CPUAffinity *string `json:"affinity,omitempty" url:"affinity,omitempty"`
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
Delete []string `json:"delete,omitempty" url:"delete,omitempty,comma"`
@@ -288,7 +288,7 @@ type CreateRequestBody struct {
TPMState *CustomTPMState `json:"tpmstate0,omitempty" url:"tpmstate0,omitempty"`
USBDevices CustomUSBDevices `json:"usb,omitempty" url:"usb,omitempty"`
VGADevice *CustomVGADevice `json:"vga,omitempty" url:"vga,omitempty"`
- VirtualCPUCount *int `json:"vcpus,omitempty" url:"vcpus,omitempty"`
+ VirtualCPUCount *int64 `json:"vcpus,omitempty" url:"vcpus,omitempty"`
VirtualIODevices CustomStorageDevices `json:"virtio,omitempty" url:"virtio,omitempty"`
VMGenerationID *string `json:"vmgenid,omitempty" url:"vmgenid,omitempty"`
VMID int `json:"vmid,omitempty" url:"vmid,omitempty"`
@@ -370,11 +370,11 @@ type GetResponseData struct {
CloudInitUsername *string `json:"ciuser,omitempty"`
CloudInitUpgrade *types.CustomBool `json:"ciupgrade,omitempty"`
CPUArchitecture *string `json:"arch,omitempty"`
- CPUCores *int `json:"cores,omitempty"`
+ CPUCores *int64 `json:"cores,omitempty"`
CPUEmulation *CustomCPUEmulation `json:"cpu,omitempty"`
- CPULimit *types.CustomInt `json:"cpulimit,omitempty"`
- CPUSockets *int `json:"sockets,omitempty"`
- CPUUnits *int `json:"cpuunits,omitempty"`
+ CPULimit *types.CustomInt64 `json:"cpulimit,omitempty"`
+ CPUSockets *int64 `json:"sockets,omitempty"`
+ CPUUnits *int64 `json:"cpuunits,omitempty"`
CPUAffinity *string `json:"affinity,omitempty"`
DedicatedMemory *types.CustomInt64 `json:"memory,omitempty"`
DeletionProtection *types.CustomBool `json:"protection,omitempty"`
@@ -523,7 +523,7 @@ type GetResponseData struct {
USBDevice2 *CustomUSBDevice `json:"usb2,omitempty"`
USBDevice3 *CustomUSBDevice `json:"usb3,omitempty"`
VGADevice *CustomVGADevice `json:"vga,omitempty"`
- VirtualCPUCount *int `json:"vcpus,omitempty"`
+ VirtualCPUCount *int64 `json:"vcpus,omitempty"`
VirtualIODevice0 *CustomStorageDevice `json:"virtio0,omitempty"`
VirtualIODevice1 *CustomStorageDevice `json:"virtio1,omitempty"`
VirtualIODevice2 *CustomStorageDevice `json:"virtio2,omitempty"`
@@ -553,7 +553,7 @@ type GetStatusResponseBody struct {
// GetStatusResponseData contains the data from a VM get status response.
type GetStatusResponseData struct {
AgentEnabled *types.CustomBool `json:"agent,omitempty"`
- CPUCount *float64 `json:"cpus,omitempty"`
+ CPUCount *int64 `json:"cpus,omitempty"`
Lock *string `json:"lock,omitempty"`
MemoryAllocation *int64 `json:"maxmem,omitempty"`
Name *string `json:"name,omitempty"`
@@ -669,7 +669,7 @@ type UpdateAsyncResponseBody struct {
// UpdateRequestBody contains the data for an virtual machine update request.
type UpdateRequestBody CreateRequestBody
-// ToDelete adds a field to the delete list.
+// ToDelete adds a field to the delete list. The field name should be the **actual** field name in the struct.
func (u *UpdateRequestBody) ToDelete(fieldName string) error {
if u == nil {
return errors.New("update request body is nil")
@@ -686,7 +686,16 @@ func (u *UpdateRequestBody) ToDelete(fieldName string) error {
return nil
}
-// EncodeValues converts a CustomAgent struct to a URL vlaue.
+// IsEmpty checks if the update request body is empty.
+func (u *UpdateRequestBody) IsEmpty() bool {
+ if u == nil {
+ return true
+ }
+
+ return reflect.DeepEqual(*u, UpdateRequestBody{})
+}
+
+// EncodeValues converts a CustomAgent struct to a URL value.
func (r CustomAgent) EncodeValues(key string, v *url.Values) error {
var values []string
@@ -717,7 +726,7 @@ func (r CustomAgent) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomAudioDevice struct to a URL vlaue.
+// EncodeValues converts a CustomAudioDevice struct to a URL value.
func (r CustomAudioDevice) EncodeValues(key string, v *url.Values) error {
values := []string{fmt.Sprintf("device=%s", r.Device)}
@@ -833,7 +842,7 @@ func (r CustomCloudInitConfig) EncodeValues(_ string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomCPUEmulation struct to a URL vlaue.
+// EncodeValues converts a CustomCPUEmulation struct to a URL value.
func (r CustomCPUEmulation) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("cputype=%s", r.Type),
@@ -860,7 +869,7 @@ func (r CustomCPUEmulation) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomEFIDisk struct to a URL vlaue.
+// EncodeValues converts a CustomEFIDisk struct to a URL value.
func (r CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
@@ -887,7 +896,7 @@ func (r CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomNetworkDevice struct to a URL vlaue.
+// EncodeValues converts a CustomNetworkDevice struct to a URL value.
func (r CustomNetworkDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("model=%s", r.Model),
@@ -961,7 +970,7 @@ func (r CustomNetworkDevices) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomNUMADevice struct to a URL vlaue.
+// EncodeValues converts a CustomNUMADevice struct to a URL value.
func (r CustomNUMADevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("cpus=%s", strings.Join(r.CPUIDs, ";")),
@@ -995,7 +1004,7 @@ func (r CustomNUMADevices) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomPCIDevice struct to a URL vlaue.
+// EncodeValues converts a CustomPCIDevice struct to a URL value.
func (r CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
values := []string{}
@@ -1068,7 +1077,7 @@ func (r CustomSerialDevices) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomSharedMemory struct to a URL vlaue.
+// EncodeValues converts a CustomSharedMemory struct to a URL value.
func (r CustomSharedMemory) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("size=%d", r.Size),
@@ -1083,7 +1092,7 @@ func (r CustomSharedMemory) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomSMBIOS struct to a URL vlaue.
+// EncodeValues converts a CustomSMBIOS struct to a URL value.
func (r CustomSMBIOS) EncodeValues(key string, v *url.Values) error {
var values []string
@@ -1130,7 +1139,7 @@ func (r CustomSMBIOS) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomSpiceEnhancements struct to a URL vlaue.
+// EncodeValues converts a CustomSpiceEnhancements struct to a URL value.
func (r CustomSpiceEnhancements) EncodeValues(key string, v *url.Values) error {
var values []string
@@ -1153,7 +1162,7 @@ func (r CustomSpiceEnhancements) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomStartupOrder struct to a URL vlaue.
+// EncodeValues converts a CustomStartupOrder struct to a URL value.
func (r CustomStartupOrder) EncodeValues(key string, v *url.Values) error {
var values []string
@@ -1176,7 +1185,7 @@ func (r CustomStartupOrder) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomTPMState struct to a URL vlaue.
+// EncodeValues converts a CustomTPMState struct to a URL value.
func (r CustomTPMState) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
@@ -1191,7 +1200,7 @@ func (r CustomTPMState) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomUSBDevice struct to a URL vlaue.
+// EncodeValues converts a CustomUSBDevice struct to a URL value.
func (r CustomUSBDevice) EncodeValues(key string, v *url.Values) error {
if r.HostDevice == nil && r.Mapping == nil {
return fmt.Errorf("either device ID or resource mapping must be set")
@@ -1229,7 +1238,7 @@ func (r CustomUSBDevices) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomVGADevice struct to a URL vlaue.
+// EncodeValues converts a CustomVGADevice struct to a URL value.
func (r CustomVGADevice) EncodeValues(key string, v *url.Values) error {
var values []string
@@ -1246,7 +1255,7 @@ func (r CustomVGADevice) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomVirtualIODevice struct to a URL vlaue.
+// EncodeValues converts a CustomVirtualIODevice struct to a URL value.
func (r CustomVirtualIODevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
@@ -1282,7 +1291,7 @@ func (r CustomVirtualIODevices) EncodeValues(key string, v *url.Values) error {
return nil
}
-// EncodeValues converts a CustomWatchdogDevice struct to a URL vlaue.
+// EncodeValues converts a CustomWatchdogDevice struct to a URL value.
func (r CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("model=%+v", r.Model),
diff --git a/proxmox/types/common_types.go b/proxmox/types/common_types.go
index fdff1181..1dd75a91 100644
--- a/proxmox/types/common_types.go
+++ b/proxmox/types/common_types.go
@@ -147,6 +147,11 @@ func (r *CustomInt64) UnmarshalJSON(b []byte) error {
return nil
}
+// PointerInt64 returns a pointer to an int64.
+func (r *CustomInt64) PointerInt64() *int64 {
+ return (*int64)(r)
+}
+
// MarshalJSON converts a boolean to a JSON value.
func (r *CustomLineBreakSeparatedList) MarshalJSON() ([]byte, error) {
s := strings.Join(*r, "\n")
diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go
index c3369fd4..e2199a47 100644
--- a/proxmoxtf/resource/vm/vm.go
+++ b/proxmoxtf/resource/vm/vm.go
@@ -1998,25 +1998,25 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
updateBody.CPUArchitecture = &cpuArchitecture
}
- updateBody.CPUCores = &cpuCores
+ updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
updateBody.CPUEmulation = &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
updateBody.NUMAEnabled = &cpuNUMA
- updateBody.CPUSockets = &cpuSockets
- updateBody.CPUUnits = &cpuUnits
+ updateBody.CPUSockets = ptr.Ptr(int64(cpuSockets))
+ updateBody.CPUUnits = ptr.Ptr(int64(cpuUnits))
if cpuAffinity != "" {
updateBody.CPUAffinity = &cpuAffinity
}
if cpuHotplugged > 0 {
- updateBody.VirtualCPUCount = &cpuHotplugged
+ updateBody.VirtualCPUCount = ptr.Ptr(int64(cpuHotplugged))
}
if cpuLimit > 0 {
- updateBody.CPULimit = &cpuLimit
+ updateBody.CPULimit = ptr.Ptr(int64(cpuLimit))
}
}
@@ -2648,13 +2648,13 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
Order: &bootOrderConverted,
},
CloudInitConfig: initializationConfig,
- CPUCores: &cpuCores,
+ CPUCores: ptr.Ptr(int64(cpuCores)),
CPUEmulation: &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
},
- CPUSockets: &cpuSockets,
- CPUUnits: &cpuUnits,
+ CPUSockets: ptr.Ptr(int64(cpuSockets)),
+ CPUUnits: ptr.Ptr(int64(cpuUnits)),
DedicatedMemory: &memoryDedicated,
DeletionProtection: &protection,
EFIDisk: efiDisk,
@@ -2703,11 +2703,11 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
}
if cpuHotplugged > 0 {
- createBody.VirtualCPUCount = &cpuHotplugged
+ createBody.VirtualCPUCount = ptr.Ptr(int64(cpuHotplugged))
}
if cpuLimit > 0 {
- createBody.CPULimit = &cpuLimit
+ createBody.CPULimit = ptr.Ptr(int64(cpuLimit))
}
if cpuAffinity != "" {
@@ -3608,21 +3608,21 @@ func vmReadCustom(
}
if vmConfig.CPUCores != nil {
- cpu[mkCPUCores] = *vmConfig.CPUCores
+ cpu[mkCPUCores] = int(*vmConfig.CPUCores)
} else {
// Default value of "cores" is "1" according to the API documentation.
cpu[mkCPUCores] = 1
}
if vmConfig.VirtualCPUCount != nil {
- cpu[mkCPUHotplugged] = *vmConfig.VirtualCPUCount
+ cpu[mkCPUHotplugged] = int(*vmConfig.VirtualCPUCount)
} else {
// Default value of "vcpus" is "1" according to the API documentation.
cpu[mkCPUHotplugged] = 0
}
if vmConfig.CPULimit != nil {
- cpu[mkCPULimit] = *vmConfig.CPULimit
+ cpu[mkCPULimit] = int(*vmConfig.CPULimit)
} else {
// Default value of "cpulimit" is "0" according to the API documentation.
cpu[mkCPULimit] = 0
@@ -3673,7 +3673,7 @@ func vmReadCustom(
}
if vmConfig.CPUSockets != nil {
- cpu[mkCPUSockets] = *vmConfig.CPUSockets
+ cpu[mkCPUSockets] = int(*vmConfig.CPUSockets)
} else {
// Default value of "sockets" is "1" according to the API documentation.
cpu[mkCPUSockets] = 1
@@ -3700,7 +3700,7 @@ func vmReadCustom(
}
if vmConfig.CPUUnits != nil {
- cpu[mkCPUUnits] = *vmConfig.CPUUnits
+ cpu[mkCPUUnits] = int(*vmConfig.CPUUnits)
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkCPUUnits] = 1024
@@ -4999,9 +4999,9 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
updateBody.CPUArchitecture = &cpuArchitecture
}
- updateBody.CPUCores = &cpuCores
- updateBody.CPUSockets = &cpuSockets
- updateBody.CPUUnits = &cpuUnits
+ updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
+ updateBody.CPUSockets = ptr.Ptr(int64(cpuSockets))
+ updateBody.CPUUnits = ptr.Ptr(int64(cpuUnits))
updateBody.NUMAEnabled = &cpuNUMA
// CPU affinity is a special case, only root can change it.
@@ -5016,13 +5016,13 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
}
if cpuHotplugged > 0 {
- updateBody.VirtualCPUCount = &cpuHotplugged
+ updateBody.VirtualCPUCount = ptr.Ptr(int64(cpuHotplugged))
} else {
del = append(del, "vcpus")
}
if cpuLimit > 0 {
- updateBody.CPULimit = &cpuLimit
+ updateBody.CPULimit = ptr.Ptr(int64(cpuLimit))
} else {
del = append(del, "cpulimit")
}
diff --git a/qodana.yaml b/qodana.yaml
index 88af6d93..ddabe094 100644
--- a/qodana.yaml
+++ b/qodana.yaml
@@ -2,6 +2,7 @@ version: "1.0"
linter: jetbrains/qodana-go:2023.3
include:
- name: CheckDependencyLicenses
+ - name: RegExpRedundantEscape
exclude:
- name: All
paths: