0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00

chore(vm2): add datasource implementation (#1318)

* chore(vm2): add datasource implementation

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-05-21 22:06:41 -04:00 committed by GitHub
parent 98233afabc
commit e3dd31f55e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 430 additions and 99 deletions

View File

@ -0,0 +1,69 @@
---
layout: page
title: proxmox_virtual_environment_vm2
parent: Data Sources
subcategory: Virtual Environment
description: |-
This is an experimental implementation of a Proxmox VM datasource using Plugin Framework.
---
# Data Source: proxmox_virtual_environment_vm2
!> **DO NOT USE**
This is an experimental implementation of a Proxmox VM datasource using Plugin Framework.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `id` (Number) The unique identifier of the VM in the Proxmox cluster.
- `node_name` (String) The name of the node where the VM is provisioned.
### 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.
- `name` (String) The name of the VM.
- `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))
<a id="nestedatt--clone"></a>
### Nested Schema for `clone`
Required:
- `id` (Number) The ID of the VM to clone.
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) List of host cores used to execute guest processes, for example: '0,5,8-11'
- `architecture` (String) The CPU architecture.
- `cores` (Number) The number of CPU cores per socket.
- `flags` (Set of String) Set of additional CPU flags.
- `hotplugged` (Number) The number of hotplugged vCPUs.
- `limit` (Number) Limit of CPU usage.
- `numa` (Boolean) Enable NUMA.
- `sockets` (Number) The number of CPU sockets.
- `type` (String) Emulated CPU type.
- `units` (Number) CPU weight for a VM
<a id="nestedatt--timeouts"></a>
### Nested Schema for `timeouts`
Optional:
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.

View File

@ -35,7 +35,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.
- `tags` (Set of String) The tags assigned to the resource.
- `tags` (Set of String) The tags assigned to the VM.
- `template` (Boolean) Set to true to create a VM template.
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))

View File

@ -441,30 +441,31 @@ func (p *proxmoxProvider) Configure(
func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewClusterOptionsResource,
NewDownloadFileResource,
access.NewACLResource,
access.NewUserTokenResource,
ha.NewHAGroupResource,
ha.NewHAResourceResource,
hardwaremapping.NewResourcePCI,
hardwaremapping.NewResourceUSB,
network.NewLinuxBridgeResource,
network.NewLinuxVLANResource,
access.NewUserTokenResource,
vm.NewVMResource,
NewClusterOptionsResource,
NewDownloadFileResource,
access.NewACLResource,
vm.NewResource,
}
}
func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
ha.NewHAGroupsDataSource,
NewVersionDataSource,
ha.NewHAGroupDataSource,
ha.NewHAResourcesDataSource,
ha.NewHAGroupsDataSource,
ha.NewHAResourceDataSource,
ha.NewHAResourcesDataSource,
hardwaremapping.NewDataSource,
hardwaremapping.NewDataSourcePCI,
hardwaremapping.NewDataSourceUSB,
hardwaremapping.NewDataSource,
NewVersionDataSource,
vm.NewDataSource,
}
}

View File

@ -0,0 +1,72 @@
package cpu
import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
// DataSourceSchema defines the schema for the CPU resource.
func DataSourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
CustomType: basetypes.ObjectType{
AttrTypes: attributeTypes(),
},
Description: "The CPU configuration.",
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"affinity": schema.StringAttribute{
Description: "List of host cores used to execute guest processes, for example: '0,5,8-11'",
Optional: true,
Computed: true,
},
"architecture": schema.StringAttribute{
Description: "The CPU architecture.",
Optional: true,
Computed: true,
},
"cores": schema.Int64Attribute{
Description: "The number of CPU cores per socket.",
Optional: true,
Computed: true,
},
"flags": schema.SetAttribute{
Description: "Set of additional CPU flags.",
Optional: true,
Computed: true,
ElementType: types.StringType,
},
"hotplugged": schema.Int64Attribute{
Description: "The number of hotplugged vCPUs.",
Optional: true,
Computed: true,
},
"limit": schema.Int64Attribute{
Description: "Limit of CPU usage.",
Optional: true,
Computed: true,
},
"numa": schema.BoolAttribute{
Description: "Enable NUMA.",
Optional: true,
Computed: true,
},
"sockets": schema.Int64Attribute{
Description: "The number of CPU sockets.",
Optional: true,
Computed: true,
},
"type": schema.StringAttribute{
Description: "Emulated CPU type.",
Optional: true,
Computed: true,
},
"units": schema.Int64Attribute{
Description: "CPU weight for a VM",
Optional: true,
Computed: true,
},
},
}
}

View File

@ -15,8 +15,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
// Schema defines the schema for the CPU resource.
func Schema() schema.Attribute {
// ResourceSchema defines the schema for the CPU resource.
func ResourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
CustomType: basetypes.ObjectType{
AttrTypes: attributeTypes(),
@ -202,7 +202,8 @@ func Schema() schema.Attribute {
},
},
"units": schema.Int64Attribute{
Description: "CPU weight for a VM. Argument is used in the kernel fair scheduler. " +
Description: "CPU weight for a VM.",
MarkdownDescription: "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,

View File

@ -0,0 +1,94 @@
package vm
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/proxmox"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &Datasource{}
_ datasource.DataSourceWithConfigure = &Datasource{}
)
// Datasource is the implementation of VM datasource.
type Datasource struct {
client proxmox.Client
}
// NewDataSource creates a new VM datasource.
func NewDataSource() datasource.DataSource {
return &Datasource{}
}
// Metadata defines the name of the resource.
func (d *Datasource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_vm2"
}
// Configure sets the client for the resource.
func (d *Datasource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(proxmox.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
)
return
}
d.client = client
}
//nolint:dupl
func (d *Datasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config Model
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}
timeout, diags := config.Timeouts.Read(ctx, defaultReadTimeout)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
exists := read(ctx, d.client, &config, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
if !exists {
tflog.Info(ctx, "VM does not exist, removing from the state", map[string]interface{}{
"id": config.ID.ValueInt64(),
})
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, config)...)
}

View File

@ -0,0 +1,65 @@
package vm
import (
"context"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"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 (d *Datasource) Schema(
ctx context.Context,
_ datasource.SchemaRequest,
resp *datasource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "This is an experimental implementation of a Proxmox VM datasource using Plugin Framework.",
Attributes: map[string]schema.Attribute{
"clone": schema.SingleNestedAttribute{
Description: "The cloning configuration.",
Optional: true,
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Description: "The ID of the VM to clone.",
Required: true,
},
"retries": schema.Int64Attribute{
Description: "The number of retries to perform when cloning the VM (default: 3).",
Optional: true,
Computed: true,
},
},
},
"cpu": cpu.DataSourceSchema(),
"description": schema.StringAttribute{
Description: "The description of the VM.",
Optional: true,
},
"id": schema.Int64Attribute{
Required: true,
Description: "The unique identifier of the VM in the Proxmox cluster.",
},
"name": schema.StringAttribute{
Description: "The name of the VM.",
Optional: true,
},
"node_name": schema.StringAttribute{
Description: "The name of the node where the VM is provisioned.",
Required: true,
},
"tags": stringset.ResourceAttribute("The tags assigned to the VM.", ""),
"template": schema.BoolAttribute{
Description: "Whether the VM is a template.",
Optional: true,
},
"timeouts": timeouts.Attributes(ctx, timeouts.Opts{
Read: true,
}),
},
}
}

View File

@ -15,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"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"
@ -44,8 +43,8 @@ type Resource struct {
client proxmox.Client
}
// NewVMResource creates a new resource for managing VMs.
func NewVMResource() resource.Resource {
// NewResource creates a new resource for managing VMs.
func NewResource() resource.Resource {
return &Resource{}
}
@ -123,7 +122,7 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp
}
// read back the VM from the PVE API to populate computed fields
exists := r.read(ctx, &plan, &resp.Diagnostics)
exists := read(ctx, r.client, &plan, &resp.Diagnostics)
if !exists {
resp.Diagnostics.AddError("VM does not exist after creation", "")
}
@ -195,7 +194,7 @@ func (r *Resource) clone(ctx context.Context, plan Model, diags *diag.Diagnostic
NodeName: plan.NodeName,
}
r.read(ctx, &clone, diags)
read(ctx, r.client, &clone, diags)
if diags.HasError() {
return
@ -204,6 +203,7 @@ func (r *Resource) clone(ctx context.Context, plan Model, diags *diag.Diagnostic
r.update(ctx, plan, clone, true, diags)
}
//nolint:dupl
func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state Model
@ -219,7 +219,7 @@ func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *res
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
exists := r.read(ctx, &state, &resp.Diagnostics)
exists := read(ctx, r.client, &state, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
@ -258,7 +258,7 @@ func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp
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)
exists := read(ctx, r.client, &plan, &resp.Diagnostics)
if !exists {
resp.Diagnostics.AddError("VM does not exist after update", "")
}
@ -423,7 +423,7 @@ func (r *Resource) ImportState(
Timeouts: ts,
}
exists := r.read(ctx, &state, &resp.Diagnostics)
exists := read(ctx, r.client, &state, &resp.Diagnostics)
if !exists {
resp.Diagnostics.AddError(fmt.Sprintf("VM %d does not exist on node %s", id, nodeName), "")
}
@ -436,48 +436,6 @@ func (r *Resource) ImportState(
resp.Diagnostics.Append(diags...)
}
// 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 *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.
config, err := vmAPI.GetVM(ctx)
if err != nil {
if errors.Is(err, api.ErrResourceDoesNotExist) {
tflog.Info(ctx, "VM does not exist, removing from the state", map[string]interface{}{
"vm_id": vmAPI.VMID,
})
} else {
diags.AddError("Failed to get VM", err.Error())
}
return false
}
status, err := vmAPI.GetVMStatus(ctx)
if err != nil {
diags.AddError("Failed to get VM status", err.Error())
return false
}
if status.VMID == nil {
diags.AddError("VM ID is missing in status API response", "")
return false
}
model.ID = types.Int64Value(int64(*status.VMID))
// 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)
model.CPU = cpu.NewValue(ctx, config, diags)
model.Tags = stringset.NewValue(config.Tags, diags)
model.Template = types.BoolPointerValue(config.Template.PointerBool())
return true
}
// Shutdown the VM, then wait for it to actually shut down (it may not be shut down immediately if
// running in HA mode).
func vmShutdown(ctx context.Context, vmAPI *vms.Client) error {

View File

@ -50,7 +50,7 @@ func (r *Resource) Schema(
},
},
},
"cpu": cpu.Schema(),
"cpu": cpu.ResourceSchema(),
"description": schema.StringAttribute{
Description: "The description of the VM.",
Optional: true,
@ -79,7 +79,7 @@ func (r *Resource) Schema(
Description: "The name of the node where the VM is provisioned.",
Required: true,
},
"tags": stringset.ResourceAttribute("The tags assigned to the resource.", ""),
"tags": stringset.ResourceAttribute("The tags assigned to the VM.", ""),
"template": schema.BoolAttribute{
Description: "Set to true to create a VM template.",
Optional: true,

View File

@ -1,28 +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/stringset"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
)
// Model represents the VM model.
//
// Note: for computed fields / blocks we have to use an Object type (or an alias),
// or a custom type in order to hold an unknown value.
type Model struct {
Description types.String `tfsdk:"description"`
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"`
}

77
fwprovider/vm/vm_model.go Normal file
View File

@ -0,0 +1,77 @@
package vm
import (
"context"
"errors"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"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"
)
// Model represents the VM model.
//
// Note: for computed fields / blocks we have to use an Object type (or an alias),
// or a custom type in order to hold an unknown value.
type Model struct {
Description types.String `tfsdk:"description"`
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"`
}
// 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 read(ctx context.Context, client proxmox.Client, model *Model, diags *diag.Diagnostics) bool {
vmAPI := client.Node(model.NodeName.ValueString()).VM(int(model.ID.ValueInt64()))
// Retrieve the entire configuration in order to compare it to the state.
config, err := vmAPI.GetVM(ctx)
if err != nil {
if errors.Is(err, api.ErrResourceDoesNotExist) {
tflog.Info(ctx, "VM does not exist, removing from the state", map[string]interface{}{
"vm_id": vmAPI.VMID,
})
} else {
diags.AddError("Failed to get VM", err.Error())
}
return false
}
status, err := vmAPI.GetVMStatus(ctx)
if err != nil {
diags.AddError("Failed to get VM status", err.Error())
return false
}
if status.VMID == nil {
diags.AddError("VM ID is missing in status API response", "")
return false
}
model.ID = types.Int64Value(int64(*status.VMID))
// 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)
model.CPU = cpu.NewValue(ctx, config, diags)
model.Tags = stringset.NewValue(config.Tags, diags)
model.Template = types.BoolPointerValue(config.Template.PointerBool())
return true
}

View File

@ -0,0 +1,21 @@
---
layout: page
title: {{.Name}}
parent: Data Sources
subcategory: Virtual Environment
description: |-
{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---
# {{.Type}}: {{.Name}}
!> **DO NOT USE**
{{ .Description | trimspace }}
{{ if .HasExample -}}
## Example Usage
{{ codefile "terraform" .ExampleFile }}
{{- end }}
{{ .SchemaMarkdown | trimspace }}

View File

@ -29,7 +29,6 @@ import (
// Temporary: while migrating to the TF framework, we need to copy the generated docs to the right place
// for the resources / data sources that have been migrated.
//go:generate cp -R ../build/docs-gen/guides/ ../docs/guides/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_version.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_hagroup.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_hagroups.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_hardware_mapping_pci.md ../docs/data-sources/
@ -37,14 +36,16 @@ import (
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_hardware_mappings.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_haresource.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_haresources.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_network_linux_bridge.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_network_linux_vlan.md ../docs/resources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_version.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_vm2.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_acl.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_cluster_options.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_download_file.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_hagroup.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_hardware_mapping_pci.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_hardware_mapping_usb.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_haresource.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_cluster_options.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_download_file.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_acl.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_network_linux_bridge.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_network_linux_vlan.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_user_token.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/