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:
parent
7d1b400641
commit
aa309fd9ea
@ -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 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 `<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`
|
||||
|
||||
|
@ -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 {
|
||||
|
63
fwprovider/tests/resource_time_test.go
Normal file
63
fwprovider/tests/resource_time_test.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
||||
|
@ -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(
|
@ -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
|
@ -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)
|
347
fwprovider/vm/cpu/resource_cpu.go
Normal file
347
fwprovider/vm/cpu/resource_cpu.go
Normal 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()
|
||||
}
|
35
fwprovider/vm/cpu/resource_cpu_model.go
Normal file
35
fwprovider/vm/cpu/resource_cpu_model.go
Normal 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,
|
||||
}
|
||||
}
|
218
fwprovider/vm/cpu/resource_cpu_schema.go
Normal file
218
fwprovider/vm/cpu/resource_cpu_schema.go
Normal 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 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 `<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),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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"`
|
||||
}
|
@ -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
|
26
fwprovider/vm/resource_vm_model.go
Normal file
26
fwprovider/vm/resource_vm_model.go
Normal 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"`
|
||||
}
|
@ -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,
|
@ -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),
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ version: "1.0"
|
||||
linter: jetbrains/qodana-go:2023.3
|
||||
include:
|
||||
- name: CheckDependencyLicenses
|
||||
- name: RegExpRedundantEscape
|
||||
exclude:
|
||||
- name: All
|
||||
paths:
|
||||
|
Loading…
Reference in New Issue
Block a user