mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 10:11:09 +00:00
feat(vm): add RNG device support (#1774)
* feat(vm): add RNG device support This commit adds support for configuring the Random Number Generator (RNG) device for virtual machines in both the VM resource and datasource. The implementation includes: - New schema and model for RNG configuration - Support for setting RNG source, max_bytes, and period - Updated documentation for RNG configuration - Test cases for RNG configuration - Integration with VM creation, update, and read operations --------- Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
3d48a97fb1
commit
3119194292
2
.gitignore
vendored
2
.gitignore
vendored
@ -48,4 +48,6 @@ terraform-provider-proxmox*
|
||||
|
||||
# VScode / Cursor
|
||||
.vscode/settings.json
|
||||
.cursor/
|
||||
.cursorrules
|
||||
coverage.out
|
||||
|
@ -28,6 +28,7 @@ This is an experimental implementation of a Proxmox VM datasource using Plugin F
|
||||
- `cpu` (Attributes) The CPU configuration. (see [below for nested schema](#nestedatt--cpu))
|
||||
- `description` (String) The description of the VM.
|
||||
- `name` (String) The name of the VM.
|
||||
- `rng` (Attributes) The RNG (Random Number Generator) configuration. (see [below for nested schema](#nestedatt--rng))
|
||||
- `tags` (Set of String) The tags assigned to the VM.
|
||||
- `template` (Boolean) Whether the VM is a template.
|
||||
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
|
||||
@ -62,6 +63,16 @@ Optional:
|
||||
- `units` (Number) CPU weight for a VM
|
||||
|
||||
|
||||
<a id="nestedatt--rng"></a>
|
||||
### Nested Schema for `rng`
|
||||
|
||||
Optional:
|
||||
|
||||
- `max_bytes` (Number) Maximum bytes of entropy allowed to get injected into the guest every period.
|
||||
- `period` (Number) Period in milliseconds to limit entropy injection to the guest.
|
||||
- `source` (String) The entropy source for the RNG device.
|
||||
|
||||
|
||||
<a id="nestedatt--timeouts"></a>
|
||||
### Nested Schema for `timeouts`
|
||||
|
||||
|
@ -484,6 +484,10 @@ output "ubuntu_vm_public_key" {
|
||||
- `pool_id` - (Optional) The identifier for a pool to assign the virtual machine to.
|
||||
- `protection` - (Optional) Sets the protection flag of the VM. This will disable the remove VM and remove disk operations (defaults to `false`).
|
||||
- `reboot` - (Optional) Reboot the VM after initial creation. (defaults to `false`)
|
||||
- `rng` - (Optional) The random number generator configuration. Can only be set by `root@pam.`
|
||||
- `source` - The file on the host to gather entropy from. In most cases, `/dev/urandom` should be preferred over `/dev/random` to avoid entropy-starvation issues on the host.
|
||||
- `max_bytes` - (Optional) Maximum bytes of entropy allowed to get injected into the guest every `period` milliseconds (defaults to `1024`). Prefer a lower value when using `/dev/random` as source.
|
||||
- `period` - (Optional) Every `period` milliseconds the entropy-injection quota is reset, allowing the guest to retrieve another `max_bytes` of entropy (defaults to `1000`).
|
||||
- `serial_device` - (Optional) A serial device (multiple blocks supported).
|
||||
- `device` - (Optional) The device (defaults to `socket`).
|
||||
- `/dev/*` - A host serial device.
|
||||
|
@ -36,6 +36,7 @@ The attributes are also marked as optional to allow the practitioner to set (or
|
||||
- `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.
|
||||
- `rng` (Attributes) Configure the RNG (Random Number Generator) device. The RNG device provides entropy to guests to ensure good quality random numbers for guest applications that require them. Can only be set by `root@pam.`See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) for more information. (see [below for nested schema](#nestedatt--rng))
|
||||
- `stop_on_destroy` (Boolean) Set to true to stop (rather than shutdown) the VM on destroy (defaults to `false`).
|
||||
- `tags` (Set of String) The tags assigned to the VM.
|
||||
- `template` (Boolean) Set to true to create a VM template.
|
||||
@ -79,6 +80,16 @@ Optional:
|
||||
- `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--rng"></a>
|
||||
### Nested Schema for `rng`
|
||||
|
||||
Optional:
|
||||
|
||||
- `max_bytes` (Number) Maximum bytes of entropy allowed to get injected into the guest every period. Use 0 to disable limiting (potentially dangerous).
|
||||
- `period` (Number) Period in milliseconds to limit entropy injection to the guest. Use 0 to disable limiting (potentially dangerous).
|
||||
- `source` (String) The file on the host to gather entropy from. In most cases, `/dev/urandom` should be preferred over `/dev/random` to avoid entropy-starvation issues on the host.
|
||||
|
||||
|
||||
<a id="nestedatt--timeouts"></a>
|
||||
### Nested Schema for `timeouts`
|
||||
|
||||
|
@ -337,6 +337,64 @@ func TestAccResourceVM(t *testing.T) {
|
||||
),
|
||||
},
|
||||
}},
|
||||
{"update rng block", []resource.TestStep{
|
||||
{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
started = false
|
||||
|
||||
rng {
|
||||
source = "/dev/urandom"
|
||||
}
|
||||
}`, WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
|
||||
"rng.0.source": "/dev/urandom",
|
||||
"rng.0.max_bytes": "1024",
|
||||
"rng.0.period": "1000",
|
||||
}),
|
||||
),
|
||||
}, {
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
started = false
|
||||
|
||||
rng {
|
||||
source = "/dev/urandom"
|
||||
max_bytes = 2048
|
||||
period = 500
|
||||
}
|
||||
}`, WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
|
||||
"rng.0.source": "/dev/urandom",
|
||||
"rng.0.max_bytes": "2048",
|
||||
"rng.0.period": "500",
|
||||
}),
|
||||
),
|
||||
}, {
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
started = false
|
||||
|
||||
rng {
|
||||
source = "/dev/random"
|
||||
max_bytes = 512
|
||||
period = 200
|
||||
}
|
||||
}`, WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
|
||||
"rng.0.source": "/dev/random",
|
||||
"rng.0.max_bytes": "512",
|
||||
"rng.0.period": "200",
|
||||
}),
|
||||
),
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
@ -9,6 +15,7 @@ import (
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/rng"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||
)
|
||||
|
||||
@ -53,6 +60,7 @@ func (d *Datasource) Schema(
|
||||
Description: "The name of the node where the VM is provisioned.",
|
||||
Required: true,
|
||||
},
|
||||
"rng": rng.DataSourceSchema(),
|
||||
"tags": stringset.ResourceAttribute("The tags assigned to the VM.", ""),
|
||||
"template": schema.BoolAttribute{
|
||||
Description: "Whether the VM is a template.",
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/rng"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
@ -38,6 +39,7 @@ type Model struct {
|
||||
ID types.Int64 `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
NodeName types.String `tfsdk:"node_name"`
|
||||
RNG rng.Value `tfsdk:"rng"`
|
||||
StopOnDestroy types.Bool `tfsdk:"stop_on_destroy"`
|
||||
Tags stringset.Value `tfsdk:"tags"`
|
||||
Template types.Bool `tfsdk:"template"`
|
||||
@ -85,6 +87,7 @@ func read(ctx context.Context, client proxmox.Client, model *Model, diags *diag.
|
||||
|
||||
// Blocks
|
||||
model.CPU = cpu.NewValue(ctx, config, diags)
|
||||
model.RNG = rng.NewValue(ctx, config, diags)
|
||||
model.VGA = vga.NewValue(ctx, config, diags)
|
||||
|
||||
model.CDROM = cdrom.NewValue(ctx, config, diags)
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/rng"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
@ -158,6 +159,7 @@ func (r *Resource) create(ctx context.Context, plan Model, diags *diag.Diagnosti
|
||||
// fill out create body fields with values from other resource blocks
|
||||
cdrom.FillCreateBody(ctx, plan.CDROM, createBody, diags)
|
||||
cpu.FillCreateBody(ctx, plan.CPU, createBody, diags)
|
||||
rng.FillCreateBody(ctx, plan.RNG, createBody, diags)
|
||||
vga.FillCreateBody(ctx, plan.VGA, createBody, diags)
|
||||
|
||||
if diags.HasError() {
|
||||
@ -205,6 +207,8 @@ func (r *Resource) clone(ctx context.Context, plan Model, diags *diag.Diagnostic
|
||||
Name: plan.Name,
|
||||
Description: plan.Description,
|
||||
NodeName: plan.NodeName,
|
||||
RNG: plan.RNG,
|
||||
VGA: plan.VGA,
|
||||
}
|
||||
|
||||
read(ctx, r.client, &clone, diags)
|
||||
@ -336,6 +340,7 @@ func (r *Resource) update(ctx context.Context, plan, state Model, isClone bool,
|
||||
// fill out update body fields with values from other resource blocks
|
||||
cdrom.FillUpdateBody(ctx, plan.CDROM, state.CDROM, updateBody, isClone, diags)
|
||||
cpu.FillUpdateBody(ctx, plan.CPU, state.CPU, updateBody, isClone, diags)
|
||||
rng.FillUpdateBody(ctx, plan.RNG, state.RNG, updateBody, isClone, diags)
|
||||
vga.FillUpdateBody(ctx, plan.VGA, state.VGA, updateBody, isClone, diags)
|
||||
|
||||
if !updateBody.IsEmpty() {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/rng"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
|
||||
)
|
||||
|
||||
@ -89,6 +90,7 @@ func (r *Resource) Schema(
|
||||
Description: "The name of the node where the VM is provisioned.",
|
||||
Required: true,
|
||||
},
|
||||
"rng": rng.ResourceSchema(),
|
||||
"stop_on_destroy": schema.BoolAttribute{
|
||||
Description: "Set to true to stop (rather than shutdown) the VM on destroy.",
|
||||
MarkdownDescription: "Set to true to stop (rather than shutdown) the VM on destroy (defaults to `false`).",
|
||||
|
41
fwprovider/vm/rng/datasource_schema.go
Normal file
41
fwprovider/vm/rng/datasource_schema.go
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 rng
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
)
|
||||
|
||||
// DataSourceSchema defines the schema for the RNG datasource.
|
||||
func DataSourceSchema() schema.Attribute {
|
||||
return schema.SingleNestedAttribute{
|
||||
CustomType: basetypes.ObjectType{
|
||||
AttrTypes: attributeTypes(),
|
||||
},
|
||||
Description: "The RNG (Random Number Generator) configuration.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"source": schema.StringAttribute{
|
||||
Description: "The entropy source for the RNG device.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"max_bytes": schema.Int64Attribute{
|
||||
Description: "Maximum bytes of entropy allowed to get injected into the guest every period.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"period": schema.Int64Attribute{
|
||||
Description: "Period in milliseconds to limit entropy injection to the guest.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
27
fwprovider/vm/rng/model.go
Normal file
27
fwprovider/vm/rng/model.go
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 rng
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Model represents the RNG model.
|
||||
type Model struct {
|
||||
Source types.String `tfsdk:"source"`
|
||||
MaxBytes types.Int64 `tfsdk:"max_bytes"`
|
||||
Period types.Int64 `tfsdk:"period"`
|
||||
}
|
||||
|
||||
func attributeTypes() map[string]attr.Type {
|
||||
return map[string]attr.Type{
|
||||
"source": types.StringType,
|
||||
"max_bytes": types.Int64Type,
|
||||
"period": types.Int64Type,
|
||||
}
|
||||
}
|
147
fwprovider/vm/rng/resource.go
Normal file
147
fwprovider/vm/rng/resource.go
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 rng
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
|
||||
)
|
||||
|
||||
// Value represents the type for RNG settings.
|
||||
type Value = types.Object
|
||||
|
||||
// NewValue returns a new Value with the given RNG settings from the PVE API.
|
||||
func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value {
|
||||
rng := Model{}
|
||||
|
||||
if config.RNGDevice != nil {
|
||||
rng.Source = types.StringValue(config.RNGDevice.Source)
|
||||
|
||||
if config.RNGDevice.MaxBytes != nil {
|
||||
rng.MaxBytes = types.Int64Value(int64(*config.RNGDevice.MaxBytes))
|
||||
}
|
||||
|
||||
if config.RNGDevice.Period != nil {
|
||||
rng.Period = types.Int64Value(int64(*config.RNGDevice.Period))
|
||||
}
|
||||
}
|
||||
|
||||
obj, d := types.ObjectValueFrom(ctx, attributeTypes(), rng)
|
||||
diags.Append(d...)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// createRNGDevice creates a new CustomRNGDevice from the given Model.
|
||||
func createRNGDevice(model Model, setSource bool) *vms.CustomRNGDevice {
|
||||
rngDevice := &vms.CustomRNGDevice{}
|
||||
|
||||
if setSource && !model.Source.IsUnknown() {
|
||||
rngDevice.Source = model.Source.ValueString()
|
||||
}
|
||||
|
||||
if !model.MaxBytes.IsUnknown() && model.MaxBytes.ValueInt64() != 0 {
|
||||
maxBytes := int(model.MaxBytes.ValueInt64())
|
||||
rngDevice.MaxBytes = &maxBytes
|
||||
}
|
||||
|
||||
if !model.Period.IsUnknown() && model.Period.ValueInt64() != 0 {
|
||||
period := int(model.Period.ValueInt64())
|
||||
rngDevice.Period = &period
|
||||
}
|
||||
|
||||
return rngDevice
|
||||
}
|
||||
|
||||
// FillCreateBody fills the CreateRequestBody with the RNG settings from the Value.
|
||||
//
|
||||
// In the 'create' context, v is the plan.
|
||||
func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
|
||||
var plan Model
|
||||
|
||||
if planValue.IsNull() || planValue.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
d := planValue.As(ctx, &plan, basetypes.ObjectAsOptions{})
|
||||
diags.Append(d...)
|
||||
|
||||
if d.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
rngDevice := createRNGDevice(plan, true)
|
||||
|
||||
if !reflect.DeepEqual(rngDevice, &vms.CustomRNGDevice{}) {
|
||||
body.RNGDevice = rngDevice
|
||||
}
|
||||
}
|
||||
|
||||
// FillUpdateBody fills the UpdateRequestBody with the RNG settings from the Value.
|
||||
//
|
||||
// In the 'update' context, v is the plan and stateValue is the current state.
|
||||
func FillUpdateBody(
|
||||
ctx context.Context,
|
||||
planValue, stateValue Value,
|
||||
updateBody *vms.UpdateRequestBody,
|
||||
isClone bool,
|
||||
diags *diag.Diagnostics,
|
||||
) {
|
||||
var plan, state Model
|
||||
|
||||
if planValue.IsNull() || planValue.IsUnknown() || planValue.Equal(stateValue) {
|
||||
return
|
||||
}
|
||||
|
||||
d := planValue.As(ctx, &plan, basetypes.ObjectAsOptions{})
|
||||
diags.Append(d...)
|
||||
d = stateValue.As(ctx, &state, basetypes.ObjectAsOptions{})
|
||||
diags.Append(d...)
|
||||
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
rngDevice := createRNGDevice(state, true)
|
||||
|
||||
if !plan.Source.Equal(state.Source) {
|
||||
if attribute.ShouldBeRemoved(plan.Source, state.Source, isClone) {
|
||||
rngDevice.Source = ""
|
||||
} else if attribute.IsDefined(plan.Source) {
|
||||
rngDevice.Source = plan.Source.ValueString()
|
||||
}
|
||||
}
|
||||
|
||||
if !plan.MaxBytes.Equal(state.MaxBytes) {
|
||||
if attribute.ShouldBeRemoved(plan.MaxBytes, state.MaxBytes, isClone) {
|
||||
rngDevice.MaxBytes = nil
|
||||
} else if attribute.IsDefined(plan.MaxBytes) {
|
||||
maxBytes := int(plan.MaxBytes.ValueInt64())
|
||||
rngDevice.MaxBytes = &maxBytes
|
||||
}
|
||||
}
|
||||
|
||||
if !plan.Period.Equal(state.Period) {
|
||||
if attribute.ShouldBeRemoved(plan.Period, state.Period, isClone) {
|
||||
rngDevice.Period = nil
|
||||
} else if attribute.IsDefined(plan.Period) {
|
||||
period := int(plan.Period.ValueInt64())
|
||||
rngDevice.Period = &period
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rngDevice, &vms.CustomRNGDevice{}) {
|
||||
updateBody.RNGDevice = rngDevice
|
||||
}
|
||||
}
|
70
fwprovider/vm/rng/resource_schema.go
Normal file
70
fwprovider/vm/rng/resource_schema.go
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 rng
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"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/basetypes"
|
||||
)
|
||||
|
||||
// ResourceSchema defines the schema for the RNG resource.
|
||||
func ResourceSchema() schema.Attribute {
|
||||
return schema.SingleNestedAttribute{
|
||||
CustomType: basetypes.ObjectType{
|
||||
AttrTypes: attributeTypes(),
|
||||
},
|
||||
Description: "The RNG (Random Number Generator) configuration. Can only be set by `root@pam.`",
|
||||
MarkdownDescription: "Configure the RNG (Random Number Generator) device. The RNG device provides entropy " +
|
||||
"to guests to ensure good quality random numbers for guest applications that require them. " +
|
||||
"Can only be set by `root@pam.`" +
|
||||
"See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) " +
|
||||
"for more information.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Object{
|
||||
objectplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"source": schema.StringAttribute{
|
||||
Description: "The entropy source for the RNG device.",
|
||||
MarkdownDescription: "The file on the host to gather entropy from. " +
|
||||
"In most cases, `/dev/urandom` should be preferred over `/dev/random` " +
|
||||
"to avoid entropy-starvation issues on the host.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthAtLeast(1),
|
||||
},
|
||||
},
|
||||
"max_bytes": schema.Int64Attribute{
|
||||
Description: "Maximum bytes of entropy allowed to get injected into the guest every period.",
|
||||
MarkdownDescription: "Maximum bytes of entropy allowed to get injected into the guest every period. " +
|
||||
"Use 0 to disable limiting (potentially dangerous).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(0),
|
||||
},
|
||||
},
|
||||
"period": schema.Int64Attribute{
|
||||
Description: "Period in milliseconds to limit entropy injection to the guest.",
|
||||
MarkdownDescription: "Period in milliseconds to limit entropy injection to the guest. " +
|
||||
"Use 0 to disable limiting (potentially dangerous).",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.Int64{
|
||||
int64validator.AtLeast(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
167
fwprovider/vm/rng/resource_test.go
Normal file
167
fwprovider/vm/rng/resource_test.go
Normal file
@ -0,0 +1,167 @@
|
||||
//go:build acceptance || all
|
||||
|
||||
/*
|
||||
* 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 rng_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
||||
)
|
||||
|
||||
func TestAccResourceVM2RNG(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
te := test.InitEnvironment(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
steps []resource.TestStep
|
||||
}{
|
||||
{"create VM with no rng params", []resource.TestStep{{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
}`),
|
||||
Check: test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
|
||||
"rng.source",
|
||||
"rng.max_bytes",
|
||||
"rng.period",
|
||||
}),
|
||||
}}},
|
||||
{"create VM with some rng params", []resource.TestStep{{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
rng = {
|
||||
source = "/dev/urandom"
|
||||
}
|
||||
}`, test.WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
|
||||
"rng.source": "/dev/urandom",
|
||||
}),
|
||||
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
|
||||
"rng.max_bytes",
|
||||
"rng.period",
|
||||
}),
|
||||
),
|
||||
}}},
|
||||
{"create VM with RNG params and then update them", []resource.TestStep{
|
||||
{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
rng = {
|
||||
source = "/dev/urandom"
|
||||
max_bytes = 1024
|
||||
}
|
||||
}`, test.WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
|
||||
"rng.source": "/dev/urandom",
|
||||
"rng.max_bytes": "1024",
|
||||
}),
|
||||
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
|
||||
"rng.period",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{ // now update the rng params and check if they are updated
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
rng = {
|
||||
source = "/dev/random"
|
||||
period = 1000
|
||||
}
|
||||
}`, test.WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
|
||||
"rng.source": "/dev/random",
|
||||
"rng.period": "1000",
|
||||
}),
|
||||
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
|
||||
"rng.max_bytes",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
RefreshState: true,
|
||||
},
|
||||
}},
|
||||
{"clone VM with some rng params", []resource.TestStep{{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "template_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "template-rng"
|
||||
rng = {
|
||||
source = "/dev/urandom"
|
||||
max_bytes = 1024
|
||||
}
|
||||
}
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
clone = {
|
||||
id = proxmox_virtual_environment_vm2.template_vm.id
|
||||
}
|
||||
}`, test.WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
|
||||
"rng.source": "/dev/urandom",
|
||||
"rng.max_bytes": "1024",
|
||||
}),
|
||||
),
|
||||
}}},
|
||||
{"clone VM with some rng params and updating them in the clone", []resource.TestStep{{
|
||||
Config: te.RenderConfig(`
|
||||
resource "proxmox_virtual_environment_vm2" "template_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "template-rng"
|
||||
rng = {
|
||||
source = "/dev/urandom"
|
||||
max_bytes = 1024
|
||||
}
|
||||
}
|
||||
resource "proxmox_virtual_environment_vm2" "test_vm" {
|
||||
node_name = "{{.NodeName}}"
|
||||
name = "test-rng"
|
||||
clone = {
|
||||
id = proxmox_virtual_environment_vm2.template_vm.id
|
||||
}
|
||||
rng = {
|
||||
source = "/dev/random"
|
||||
period = 2000
|
||||
}
|
||||
}`, test.WithRootUser()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
|
||||
"rng.source": "/dev/random",
|
||||
"rng.period": "2000",
|
||||
"rng.max_bytes": "1024",
|
||||
}),
|
||||
),
|
||||
}}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: te.AccProviders,
|
||||
Steps: tt.steps,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vga
|
||||
|
||||
import (
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vga
|
||||
|
||||
import (
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vga
|
||||
|
||||
import (
|
||||
|
86
proxmox/nodes/vms/custom_rng_device.go
Normal file
86
proxmox/nodes/vms/custom_rng_device.go
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 vms
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CustomRNGDevice represents a random number generator device configuration.
|
||||
type CustomRNGDevice struct {
|
||||
Source string `json:"source,omitempty" url:"source,omitempty"`
|
||||
MaxBytes *int `json:"max_bytes,omitempty" url:"max_bytes,omitempty"`
|
||||
Period *int `json:"period,omitempty" url:"period,omitempty"`
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomRNGDevice struct to a URL value.
|
||||
func (r *CustomRNGDevice) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.Source != "" {
|
||||
values = append(values, fmt.Sprintf("source=%s", r.Source))
|
||||
}
|
||||
|
||||
if r.MaxBytes != nil {
|
||||
values = append(values, fmt.Sprintf("max_bytes=%d", *r.MaxBytes))
|
||||
}
|
||||
|
||||
if r.Period != nil {
|
||||
values = append(values, fmt.Sprintf("period=%d", *r.Period))
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a JSON object into a CustomRNGDevice struct.
|
||||
func (r *CustomRNGDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomRNGDevice: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
if len(v) == 1 {
|
||||
r.Source = v[0]
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "source":
|
||||
r.Source = v[1]
|
||||
|
||||
case "max_bytes":
|
||||
maxBytes, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse max_bytes: %w", err)
|
||||
}
|
||||
|
||||
r.MaxBytes = &maxBytes
|
||||
|
||||
case "period":
|
||||
period, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse period: %w", err)
|
||||
}
|
||||
|
||||
r.Period = &period
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
158
proxmox/nodes/vms/custom_rng_device_test.go
Normal file
158
proxmox/nodes/vms/custom_rng_device_test.go
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 vms
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
)
|
||||
|
||||
func TestCustomRNGDevice_UnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want *CustomRNGDevice
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "source only",
|
||||
line: `"source=urandom"`,
|
||||
want: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all options",
|
||||
line: `"source=/dev/random,max_bytes=1024,period=1000"`,
|
||||
want: &CustomRNGDevice{
|
||||
Source: "/dev/random",
|
||||
MaxBytes: ptr.Ptr(1024),
|
||||
Period: ptr.Ptr(1000),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source with max_bytes",
|
||||
line: `"source=urandom,max_bytes=2048"`,
|
||||
want: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
MaxBytes: ptr.Ptr(2048),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source with period",
|
||||
line: `"source=urandom,period=2000"`,
|
||||
want: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
Period: ptr.Ptr(2000),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
line: `{"source": "urandom"}`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid max_bytes",
|
||||
line: `"source=urandom,max_bytes=invalid"`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid period",
|
||||
line: `"source=urandom,period=invalid"`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "single value source",
|
||||
line: `"urandom"`,
|
||||
want: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := &CustomRNGDevice{}
|
||||
err := r.UnmarshalJSON([]byte(tt.line))
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomRNGDevice_EncodeValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
device *CustomRNGDevice
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "source only",
|
||||
device: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
},
|
||||
key: "rng0",
|
||||
expected: "source=urandom",
|
||||
},
|
||||
{
|
||||
name: "all options",
|
||||
device: &CustomRNGDevice{
|
||||
Source: "/dev/random",
|
||||
MaxBytes: ptr.Ptr(1024),
|
||||
Period: ptr.Ptr(1000),
|
||||
},
|
||||
key: "rng0",
|
||||
expected: "source=/dev/random,max_bytes=1024,period=1000",
|
||||
},
|
||||
{
|
||||
name: "source with max_bytes",
|
||||
device: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
MaxBytes: ptr.Ptr(2048),
|
||||
},
|
||||
key: "rng0",
|
||||
expected: "source=urandom,max_bytes=2048",
|
||||
},
|
||||
{
|
||||
name: "source with period",
|
||||
device: &CustomRNGDevice{
|
||||
Source: "urandom",
|
||||
Period: ptr.Ptr(2000),
|
||||
},
|
||||
key: "rng0",
|
||||
expected: "source=urandom,period=2000",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
values := &url.Values{}
|
||||
err := tt.device.EncodeValues(tt.key, values)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expected, values.Get(tt.key))
|
||||
})
|
||||
}
|
||||
}
|
@ -44,13 +44,13 @@ type CreateRequestBody struct {
|
||||
Boot *CustomBoot `json:"boot,omitempty" url:"boot,omitempty"`
|
||||
CDROM *string `json:"cdrom,omitempty" url:"cdrom,omitempty"`
|
||||
CloudInitConfig *CustomCloudInitConfig `json:"cloudinit,omitempty" url:"cloudinit,omitempty"`
|
||||
CPUAffinity *string `json:"affinity,omitempty" url:"affinity,omitempty"`
|
||||
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
|
||||
CPUCores *int64 `json:"cores,omitempty" url:"cores,omitempty"`
|
||||
CPUEmulation *CustomCPUEmulation `json:"cpu,omitempty" url:"cpu,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"`
|
||||
DeletionProtection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
|
||||
@ -80,6 +80,7 @@ type CreateRequestBody struct {
|
||||
PCIDevices CustomPCIDevices `json:"hostpci,omitempty" url:"hostpci,omitempty"`
|
||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
||||
Revert *string `json:"revert,omitempty" url:"revert,omitempty"`
|
||||
RNGDevice *CustomRNGDevice `json:"rng0,omitempty" url:"rng0,omitempty"`
|
||||
SCSIHardware *string `json:"scsihw,omitempty" url:"scsihw,omitempty"`
|
||||
SerialDevices CustomSerialDevices `json:"serial,omitempty" url:"serial,omitempty"`
|
||||
SharedMemory *CustomSharedMemory `json:"ivshmem,omitempty" url:"ivshmem,omitempty"`
|
||||
@ -290,6 +291,7 @@ type GetResponseData struct {
|
||||
Overwrite *types.CustomBool `json:"force,omitempty"`
|
||||
PoolID *string `json:"pool,omitempty"`
|
||||
Revert *string `json:"revert,omitempty"`
|
||||
RNGDevice *CustomRNGDevice `json:"rng0,omitempty"`
|
||||
SCSIHardware *string `json:"scsihw,omitempty"`
|
||||
SerialDevice0 *string `json:"serial0,omitempty"`
|
||||
SerialDevice1 *string `json:"serial1,omitempty"`
|
||||
|
@ -104,6 +104,8 @@ const (
|
||||
dvOperatingSystemType = "other"
|
||||
dvPoolID = ""
|
||||
dvProtection = false
|
||||
dvRNGMaxBytes = 1024
|
||||
dvRNGPeriod = 1000
|
||||
dvSerialDeviceDevice = "socket"
|
||||
dvSMBIOSFamily = ""
|
||||
dvSMBIOSManufacturer = ""
|
||||
@ -244,6 +246,10 @@ const (
|
||||
mkOperatingSystemType = "type"
|
||||
mkPoolID = "pool_id"
|
||||
mkProtection = "protection"
|
||||
mkRNG = "rng"
|
||||
mkRNGSource = "source"
|
||||
mkRNGMaxBytes = "max_bytes"
|
||||
mkRNGPeriod = "period"
|
||||
mkSerialDevice = "serial_device"
|
||||
mkSerialDeviceDevice = "device"
|
||||
mkSMBIOS = "smbios"
|
||||
@ -1205,6 +1211,42 @@ func VM() *schema.Resource {
|
||||
Optional: true,
|
||||
Default: dvProtection,
|
||||
},
|
||||
mkRNG: {
|
||||
Type: schema.TypeList,
|
||||
Description: "The RNG configuration",
|
||||
Optional: true,
|
||||
DefaultFunc: func() (interface{}, error) {
|
||||
return []interface{}{}, nil
|
||||
},
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
mkRNGSource: {
|
||||
Type: schema.TypeString,
|
||||
Description: "The file on the host to gather entropy from. " +
|
||||
"In most cases, `/dev/urandom` should be preferred over `/dev/random` " +
|
||||
"to avoid entropy-starvation issues on the host.",
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
Required: true,
|
||||
},
|
||||
mkRNGMaxBytes: {
|
||||
Type: schema.TypeInt,
|
||||
Description: "Maximum bytes of entropy allowed to get injected into the guest every `period` " +
|
||||
"milliseconds. Prefer a lower value when using `/dev/random` as source.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)),
|
||||
},
|
||||
mkRNGPeriod: {
|
||||
Type: schema.TypeInt,
|
||||
Description: "Every `period` milliseconds the entropy-injection quota is reset, " +
|
||||
"allowing the guest to retrieve another `max_bytes` of entropy.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mkSerialDevice: {
|
||||
Type: schema.TypeList,
|
||||
Description: "The serial devices",
|
||||
@ -2409,25 +2451,13 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
||||
}
|
||||
}
|
||||
|
||||
var tpmState *vms.CustomTPMState
|
||||
|
||||
tpmStateBlock := d.Get(mkTPMState).([]interface{})
|
||||
if len(tpmStateBlock) > 0 && tpmStateBlock[0] != nil {
|
||||
block := tpmStateBlock[0].(map[string]interface{})
|
||||
|
||||
datastoreID, _ := block[mkTPMStateDatastoreID].(string)
|
||||
version, _ := block[mkTPMStateVersion].(string)
|
||||
|
||||
if version == "" {
|
||||
version = dvTPMStateVersion
|
||||
}
|
||||
|
||||
tpmState = &vms.CustomTPMState{
|
||||
FileVolume: fmt.Sprintf("%s:1", datastoreID),
|
||||
Version: &version,
|
||||
}
|
||||
tpmState := vmGetTPMState(d, nil)
|
||||
if tpmState != nil && (tpmState.Version == nil || *tpmState.Version == "") {
|
||||
tpmState.Version = ptr.Ptr(dvTPMStateVersion)
|
||||
}
|
||||
|
||||
rng := vmGetRNGDevice(d)
|
||||
|
||||
initializationConfig := vmGetCloudInitConfig(d)
|
||||
initializationAttr := d.Get(mkInitialization)
|
||||
|
||||
@ -2655,6 +2685,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
||||
NUMADevices: numaDeviceObjects,
|
||||
OSType: &operatingSystemType,
|
||||
PCIDevices: pciDeviceObjects,
|
||||
RNGDevice: rng,
|
||||
SCSIHardware: &scsiHardware,
|
||||
SerialDevices: serialDevices,
|
||||
SharedMemory: memorySharedObject,
|
||||
@ -3048,6 +3079,36 @@ func vmGetTPMStateAsStorageDevice(d *schema.ResourceData, disk []interface{}) *v
|
||||
return storageDevice
|
||||
}
|
||||
|
||||
func vmGetRNGDevice(d *schema.ResourceData) *vms.CustomRNGDevice {
|
||||
rngBlock := d.Get(mkRNG).([]interface{})
|
||||
|
||||
var rng *vms.CustomRNGDevice
|
||||
|
||||
if len(rngBlock) > 0 && rngBlock[0] != nil {
|
||||
block := rngBlock[0].(map[string]interface{})
|
||||
|
||||
source, _ := block[mkRNGSource].(string)
|
||||
|
||||
maxBytes, _ := block[mkRNGMaxBytes].(int)
|
||||
if maxBytes == 0 {
|
||||
maxBytes = dvRNGMaxBytes
|
||||
}
|
||||
|
||||
period, _ := block[mkRNGPeriod].(int)
|
||||
if period == 0 {
|
||||
period = dvRNGPeriod
|
||||
}
|
||||
|
||||
rng = &vms.CustomRNGDevice{
|
||||
Source: source,
|
||||
MaxBytes: &maxBytes,
|
||||
Period: &period,
|
||||
}
|
||||
}
|
||||
|
||||
return rng
|
||||
}
|
||||
|
||||
func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
|
||||
pciDevice := d.Get(mkHostPCI).([]interface{})
|
||||
pciDeviceObjects := make(vms.CustomPCIDevices, len(pciDevice))
|
||||
@ -3758,6 +3819,35 @@ func vmReadCustom(
|
||||
}
|
||||
}
|
||||
|
||||
if vmConfig.RNGDevice != nil {
|
||||
rng := map[string]interface{}{}
|
||||
|
||||
rng[mkRNGSource] = vmConfig.RNGDevice.Source
|
||||
|
||||
if vmConfig.RNGDevice.MaxBytes != nil {
|
||||
rng[mkRNGMaxBytes] = *vmConfig.RNGDevice.MaxBytes
|
||||
}
|
||||
|
||||
if vmConfig.RNGDevice.Period != nil {
|
||||
rng[mkRNGPeriod] = *vmConfig.RNGDevice.Period
|
||||
}
|
||||
|
||||
currentRNG := d.Get(mkRNG).([]interface{})
|
||||
|
||||
if len(clone) > 0 {
|
||||
if len(currentRNG) > 0 {
|
||||
err := d.Set(mkRNG, []interface{}{rng})
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
}
|
||||
} else if len(currentRNG) > 0 ||
|
||||
rng[mkRNGSource] != "" ||
|
||||
rng[mkRNGMaxBytes] != dvRNGMaxBytes || // or != 0?
|
||||
rng[mkRNGPeriod] != dvRNGPeriod {
|
||||
err := d.Set(mkRNG, []interface{}{rng})
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
}
|
||||
}
|
||||
|
||||
currentPCIList := d.Get(mkHostPCI).([]interface{})
|
||||
pciMap := map[string]interface{}{}
|
||||
|
||||
@ -5039,6 +5129,15 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
||||
rebootRequired = true
|
||||
}
|
||||
|
||||
// Prepare the new RNG configuration.
|
||||
if d.HasChange(mkRNG) {
|
||||
rngDevice := vmGetRNGDevice(d)
|
||||
|
||||
updateBody.RNGDevice = rngDevice
|
||||
|
||||
rebootRequired = true
|
||||
}
|
||||
|
||||
// Prepare the new cloud-init configuration.
|
||||
stoppedBeforeUpdate := false
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user