0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-04 21:14:05 +00:00

chore(vm2): add support for cpu (#1310)

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-05-18 00:52:02 -04:00 committed by GitHub
parent 7d1b400641
commit aa309fd9ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1045 additions and 143 deletions

View File

@ -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).
<a id="nestedatt--cpu"></a>
### Nested Schema for `cpu`
Optional:
- `affinity` (String) The CPU cores that are used to run the VMs 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 VMs vCPUs are run on the first four CPU cores. Setting `affinity` is only allowed for `root@pam` authenticated user.
- `architecture` (String) The CPU architecture `<aarch64 | x86_64>` (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.
<a id="nestedatt--timeouts"></a>
### Nested Schema for `timeouts`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 VMs 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 VMs 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 `<aarch64 | x86_64>` (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),
},
},
},
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ version: "1.0"
linter: jetbrains/qodana-go:2023.3
include:
- name: CheckDependencyLicenses
- name: RegExpRedundantEscape
exclude:
- name: All
paths: