diff --git a/docs/data-sources/virtual_environment_hardware_mapping_dir.md b/docs/data-sources/virtual_environment_hardware_mapping_dir.md
new file mode 100644
index 00000000..a87488f6
--- /dev/null
+++ b/docs/data-sources/virtual_environment_hardware_mapping_dir.md
@@ -0,0 +1,45 @@
+---
+layout: page
+title: proxmox_virtual_environment_hardware_mapping_dir
+parent: Data Sources
+subcategory: Virtual Environment
+description: |-
+ Retrieves a directory mapping from a Proxmox VE cluster.
+---
+
+# Data Source: proxmox_virtual_environment_hardware_mapping_dir
+
+Retrieves a directory mapping from a Proxmox VE cluster.
+
+## Example Usage
+
+```terraform
+data "proxmox_virtual_environment_hardware_mapping_dir" "example" {
+ name = "example"
+}
+
+output "data_proxmox_virtual_environment_hardware_mapping_dir" {
+ value = data.proxmox_virtual_environment_hardware_mapping_dir.example
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of this directory mapping.
+
+### Read-Only
+
+- `comment` (String) The comment of this directory mapping.
+- `id` (String) The unique identifier of this directory mapping data source.
+- `map` (Attributes Set) The actual map of devices for the directory mapping. (see [below for nested schema](#nestedatt--map))
+
+
+### Nested Schema for `map`
+
+Read-Only:
+
+- `node` (String) The node name attribute of the map.
+- `path` (String) The path attribute of the map.
diff --git a/docs/data-sources/virtual_environment_hardware_mappings.md b/docs/data-sources/virtual_environment_hardware_mappings.md
index 98e2b65a..d2f3ec73 100644
--- a/docs/data-sources/virtual_environment_hardware_mappings.md
+++ b/docs/data-sources/virtual_environment_hardware_mappings.md
@@ -14,6 +14,11 @@ Retrieves a list of hardware mapping resources.
## Example Usage
```terraform
+data "proxmox_virtual_environment_hardware_mappings" "example-dir" {
+ check_node = "pve"
+ type = "dir"
+}
+
data "proxmox_virtual_environment_hardware_mappings" "example-pci" {
check_node = "pve"
type = "pci"
diff --git a/docs/resources/virtual_environment_hardware_mapping_dir.md b/docs/resources/virtual_environment_hardware_mapping_dir.md
new file mode 100644
index 00000000..e95bff26
--- /dev/null
+++ b/docs/resources/virtual_environment_hardware_mapping_dir.md
@@ -0,0 +1,62 @@
+---
+layout: page
+title: proxmox_virtual_environment_hardware_mapping_dir
+parent: Resources
+subcategory: Virtual Environment
+description: |-
+ Manages a directory mapping in a Proxmox VE cluster.
+---
+
+# Resource: proxmox_virtual_environment_hardware_mapping_dir
+
+Manages a directory mapping in a Proxmox VE cluster.
+
+## Example Usage
+
+```terraform
+resource "proxmox_virtual_environment_hardware_mapping_dir" "example" {
+ comment = "This is a comment"
+ name = "example"
+ # The actual map of devices.
+ map = [
+ {
+ node = "pve"
+ path = "/mnt/data"
+ },
+ ]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `map` (Attributes Set) The actual map of devices for the hardware mapping. (see [below for nested schema](#nestedatt--map))
+- `name` (String) The name of this directory mapping.
+
+### Optional
+
+- `comment` (String) The comment of this directory mapping.
+
+### Read-Only
+
+- `id` (String) The unique identifier of this directory mapping resource.
+
+
+### Nested Schema for `map`
+
+Required:
+
+- `node` (String) The node this mapping applies to.
+- `path` (String) The path of the map. For directory mappings the path is required and refers to the POSIX path of the directory as visible from the node.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+#!/usr/bin/env sh
+# A directory mapping can be imported using their name, e.g.:
+terraform import proxmox_virtual_environment_hardware_mapping_dir.example example
+```
diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf
index d6d948c1..56554b7e 100644
--- a/example/resource_virtual_environment_vm.tf
+++ b/example/resource_virtual_environment_vm.tf
@@ -240,6 +240,15 @@ resource "proxmox_virtual_environment_vm" "data_vm" {
}
}
+resource "proxmox_virtual_environment_hardware_mapping_dir" "dir_mapping" {
+ name = "terraform-provider-proxmox-dir-mapping"
+
+ map = [{
+ node = data.proxmox_virtual_environment_nodes.example.names[0]
+ path = "/mnt"
+ }]
+}
+
output "resource_proxmox_virtual_environment_vm_example_id" {
value = proxmox_virtual_environment_vm.example.id
}
diff --git a/examples/data-sources/proxmox_virtual_environment_hardware_mapping_dir/data-source.tf b/examples/data-sources/proxmox_virtual_environment_hardware_mapping_dir/data-source.tf
new file mode 100644
index 00000000..19c1339e
--- /dev/null
+++ b/examples/data-sources/proxmox_virtual_environment_hardware_mapping_dir/data-source.tf
@@ -0,0 +1,7 @@
+data "proxmox_virtual_environment_hardware_mapping_dir" "example" {
+ name = "example"
+}
+
+output "data_proxmox_virtual_environment_hardware_mapping_dir" {
+ value = data.proxmox_virtual_environment_hardware_mapping_dir.example
+}
diff --git a/examples/data-sources/proxmox_virtual_environment_hardware_mappings/data-source.tf b/examples/data-sources/proxmox_virtual_environment_hardware_mappings/data-source.tf
index 768729e3..568f25e4 100644
--- a/examples/data-sources/proxmox_virtual_environment_hardware_mappings/data-source.tf
+++ b/examples/data-sources/proxmox_virtual_environment_hardware_mappings/data-source.tf
@@ -1,3 +1,8 @@
+data "proxmox_virtual_environment_hardware_mappings" "example-dir" {
+ check_node = "pve"
+ type = "dir"
+}
+
data "proxmox_virtual_environment_hardware_mappings" "example-pci" {
check_node = "pve"
type = "pci"
diff --git a/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/import.sh b/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/import.sh
new file mode 100644
index 00000000..d04aa4c3
--- /dev/null
+++ b/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/import.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+# A directory mapping can be imported using their name, e.g.:
+terraform import proxmox_virtual_environment_hardware_mapping_dir.example example
diff --git a/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/resource.tf b/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/resource.tf
new file mode 100644
index 00000000..84bc18f1
--- /dev/null
+++ b/examples/resources/proxmox_virtual_environment_hardware_mapping_dir/resource.tf
@@ -0,0 +1,11 @@
+resource "proxmox_virtual_environment_hardware_mapping_dir" "example" {
+ comment = "This is a comment"
+ name = "example"
+ # The actual map of devices.
+ map = [
+ {
+ node = "pve"
+ path = "/mnt/data"
+ },
+ ]
+}
diff --git a/fwprovider/nodes/hardwaremapping/datasource.go b/fwprovider/nodes/hardwaremapping/datasource.go
index f5b2056e..dcbc73a6 100644
--- a/fwprovider/nodes/hardwaremapping/datasource.go
+++ b/fwprovider/nodes/hardwaremapping/datasource.go
@@ -111,10 +111,12 @@ func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest, resp
// name.
// Note that the Proxmox VE API, for whatever reason, only returns one error at a time, even though the field is an
// array.
- if (len(data.ChecksPCI) > 0) || len(data.ChecksUSB) > 0 {
+ if (len(data.Checks) > 0) || len(data.ChecksUSB) > 0 {
switch data.Type {
+ case proxmoxtypes.TypeDir:
+ hm.Checks = append(hm.Checks, createCheckDiagnostics(data.ID, data.Checks)...)
case proxmoxtypes.TypePCI:
- hm.Checks = append(hm.Checks, createCheckDiagnostics(data.ID, data.ChecksPCI)...)
+ hm.Checks = append(hm.Checks, createCheckDiagnostics(data.ID, data.Checks)...)
case proxmoxtypes.TypeUSB:
hm.Checks = append(hm.Checks, createCheckDiagnostics(data.ID, data.ChecksUSB)...)
}
@@ -188,6 +190,7 @@ func (d *dataSource) Schema(
Validators: []validator.String{
stringvalidator.OneOf(
[]string{
+ proxmoxtypes.TypeDir.String(),
proxmoxtypes.TypePCI.String(),
proxmoxtypes.TypeUSB.String(),
}...,
diff --git a/fwprovider/nodes/hardwaremapping/datasource_dir.go b/fwprovider/nodes/hardwaremapping/datasource_dir.go
new file mode 100644
index 00000000..155b9b23
--- /dev/null
+++ b/fwprovider/nodes/hardwaremapping/datasource_dir.go
@@ -0,0 +1,140 @@
+/*
+ * 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 hardwaremapping
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
+ customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
+ mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
+ proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
+)
+
+// Ensure the implementation satisfies the required interfaces.
+var (
+ _ datasource.DataSource = &dirDataSource{}
+ _ datasource.DataSourceWithConfigure = &dirDataSource{}
+)
+
+// dirDataSource is the data source implementation for a directory mapping.
+type dirDataSource struct {
+ client *mappings.Client
+}
+
+// Configure adds the provider-configured client to the data source.
+func (d *dirDataSource) Configure(
+ _ context.Context,
+ req datasource.ConfigureRequest,
+ resp *datasource.ConfigureResponse,
+) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ cfg, ok := req.ProviderData.(config.DataSource)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected DataSource Configure Type",
+ fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.client = cfg.Client.Cluster().HardwareMapping()
+}
+
+// Metadata returns the data source type name.
+func (d *dirDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_hardware_mapping_dir"
+}
+
+// Read fetches the specified directory mapping from the Proxmox VE API.
+func (d *dirDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var hm modelDir
+
+ resp.Diagnostics.Append(req.Config.Get(ctx, &hm)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ hmID := hm.Name.ValueString()
+ // Ensure to keep both in sync since the name represents the ID.
+ hm.ID = hm.Name
+
+ data, err := d.client.Get(ctx, proxmoxtypes.TypeDir, hmID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Unable to read directory mapping %q", hmID),
+ err.Error(),
+ )
+
+ return
+ }
+
+ hm.importFromAPI(ctx, data)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &hm)...)
+}
+
+// Schema defines the schema for the directory mapping.
+func (d *dirDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ comment := dataSourceSchemaBaseAttrComment
+ comment.Optional = false
+ comment.Computed = true
+ comment.Description = "The comment of this directory mapping."
+
+ resp.Schema = schema.Schema{
+ Description: "Retrieves a directory mapping from a Proxmox VE cluster.",
+ Attributes: map[string]schema.Attribute{
+ schemaAttrNameComment: comment,
+ schemaAttrNameMap: schema.SetNestedAttribute{
+ Computed: true,
+ Description: "The actual map of devices for the directory mapping.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ schemaAttrNameMapNode: schema.StringAttribute{
+ Computed: true,
+ Description: "The node name attribute of the map.",
+ },
+ schemaAttrNameMapPath: schema.StringAttribute{
+ // For directory mappings the path is required and refers
+ // to the POSIX path of the directory as visible from the node.
+ Computed: true,
+ CustomType: customtypes.PathType{},
+ Description: "The path attribute of the map.",
+ },
+ },
+ },
+ Validators: []validator.Set{
+ setvalidator.SizeAtLeast(1),
+ },
+ },
+ schemaAttrNameName: schema.StringAttribute{
+ Description: "The name of this directory mapping.",
+ Required: true,
+ },
+ schemaAttrNameTerraformID: attribute.ResourceID(
+ "The unique identifier of this directory mapping data source.",
+ ),
+ },
+ }
+}
+
+// NewDirDataSource returns a new data source for a directory mapping.
+// This is a helper function to simplify the provider implementation.
+func NewDirDataSource() datasource.DataSource {
+ return &dirDataSource{}
+}
diff --git a/fwprovider/nodes/hardwaremapping/models.go b/fwprovider/nodes/hardwaremapping/models.go
index 66ca5015..69e6c5f3 100644
--- a/fwprovider/nodes/hardwaremapping/models.go
+++ b/fwprovider/nodes/hardwaremapping/models.go
@@ -89,6 +89,15 @@ const (
schemaAttrNameHWMIDs = "ids"
)
+// modelDirMap maps the schema data for the map of a directory mapping.
+type modelDirMap struct {
+ // Node is the "node name" for the map.
+ Node types.String `tfsdk:"node"`
+
+ // Path is the "path" for the map.
+ Path customtypes.PathValue `tfsdk:"path"`
+}
+
// modelPCIMap maps the schema data for the map of a PCI hardware mapping.
type modelPCIMap struct {
// Comment is the "comment" for the map.
@@ -140,6 +149,26 @@ type modelUSBMap struct {
Path customtypes.PathValue `tfsdk:"path"`
}
+// modelDir maps the schema data for a directory mapping.
+type modelDir struct {
+ // Comment is the comment of the directory mapping.
+ // Note that the Proxmox VE API attribute is named "description", but we map it as a comment since this naming is
+ // generally across the Proxmox VE web UI and API documentations. This still follows the [Terraform "best practices"]
+ // as it improves the user experience by matching the field name to the naming used in the human-facing interfaces.
+ //
+ // [Terraform "best practices"]: https://developer.hashicorp.com/terraform/plugin/best-practices/hashicorp-provider-design-principles#resource-and-attribute-schema-should-closely-match-the-underlying-api
+ Comment types.String `tfsdk:"comment"`
+
+ // ID is the Terraform identifier.
+ ID types.String `tfsdk:"id"`
+
+ // Name is the name of the directory mapping.
+ Name types.String `tfsdk:"name"`
+
+ // Map is the map of the directory mapping.
+ Map []modelDirMap `tfsdk:"map"`
+}
+
// modelPCI maps the schema data for a PCI hardware mapping.
type modelPCI struct {
// Comment is the comment of the PCI hardware mapping.
@@ -223,6 +252,78 @@ type modelNodeCheckDiag struct {
Severity types.String `tfsdk:"severity"`
}
+// importFromAPI imports the contents of a directory mapping model from the Proxmox VE API's response data.
+func (hm *modelDir) importFromAPI(_ context.Context, data *apitypes.GetResponseData) {
+ // Ensure that both the ID and name are in sync.
+ hm.Name = hm.ID
+ // The attribute is named "description" by the Proxmox VE API, but we map it as a comment since this naming is
+ // generally across the Proxmox VE web UI and API documentations.
+ hm.Comment = types.StringPointerValue(data.Description)
+ maps := make([]modelDirMap, len(data.Map))
+
+ for idx, pveMap := range data.Map {
+ tfMap := modelDirMap{
+ Node: types.StringValue(pveMap.Node),
+ Path: customtypes.NewPathPointerValue(pveMap.Path),
+ }
+
+ maps[idx] = tfMap
+ }
+
+ hm.Map = maps
+}
+
+// toCreateRequest builds the request data structure for creating a new directory mapping.
+func (hm *modelDir) toCreateRequest() *apitypes.CreateRequestBody {
+ return &apitypes.CreateRequestBody{
+ DataBase: hm.toRequestBase(),
+ ID: hm.ID.ValueString(),
+ }
+}
+
+// toRequestBase builds the common request data structure for the directory mapping creation or update API calls.
+func (hm *modelDir) toRequestBase() apitypes.DataBase {
+ dataBase := apitypes.DataBase{
+ // The attribute is named "description" by the Proxmox VE API, but we map it as a comment since this naming is
+ // generally across the Proxmox VE web UI and API documentations.
+ Description: hm.Comment.ValueStringPointer(),
+ }
+ maps := make([]proxmoxtypes.Map, len(hm.Map))
+
+ for idx, tfMap := range hm.Map {
+ pveMap := proxmoxtypes.Map{
+ Node: tfMap.Node.ValueString(),
+ Path: tfMap.Path.ValueStringPointer(),
+ }
+
+ maps[idx] = pveMap
+ }
+
+ dataBase.Map = maps
+
+ return dataBase
+}
+
+// toUpdateRequest builds the request data structure for updating an existing USB hardware mapping.
+func (hm *modelDir) toUpdateRequest(currentState *modelDir) *apitypes.UpdateRequestBody {
+ var del []string
+
+ if hm.Comment.IsNull() && !currentState.Comment.IsNull() {
+ // The Proxmox VE API attribute is named "description" while we name it "comment" internally since this naming is
+ // generally used across the Proxmox VE web UI and API documentations.
+ // This still follows the Terraform "best practices" [1] as it improves the user experience by matching the field
+ // name to the naming used in the human-facing interfaces.
+ // References:
+ // 1. https://developer.hashicorp.com/terraform/plugin/best-practices/hashicorp-provider-design-principles#resource-and-attribute-schema-should-closely-match-the-underlying-api
+ del = append(del, proxmoxtypes.AttrNameDescription)
+ }
+
+ return &apitypes.UpdateRequestBody{
+ DataBase: hm.toRequestBase(),
+ Delete: del,
+ }
+}
+
// importFromAPI imports the contents of a PCI hardware mapping model from the Proxmox VE API's response data.
func (hm *modelPCI) importFromAPI(_ context.Context, data *apitypes.GetResponseData) {
// Ensure that both the ID and name are in sync.
diff --git a/fwprovider/nodes/hardwaremapping/resource_dir.go b/fwprovider/nodes/hardwaremapping/resource_dir.go
new file mode 100644
index 00000000..3eac6cf6
--- /dev/null
+++ b/fwprovider/nodes/hardwaremapping/resource_dir.go
@@ -0,0 +1,280 @@
+/*
+ * 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 hardwaremapping
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/tfsdk"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
+ "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
+ customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
+ mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
+ proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
+)
+
+// Ensure the resource implements the required interfaces.
+var (
+ _ resource.Resource = &dirResource{}
+ _ resource.ResourceWithConfigure = &dirResource{}
+ _ resource.ResourceWithImportState = &dirResource{}
+)
+
+// dirResource contains the directory mapping resource's internal data.
+type dirResource struct {
+ // client is the hardware mapping API client.
+ client *mappings.Client
+}
+
+// read reads information about a directory mapping from the Proxmox VE API.
+func (r *dirResource) read(ctx context.Context, hm *modelDir) (bool, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ hmName := hm.Name.ValueString()
+
+ data, err := r.client.Get(ctx, proxmoxtypes.TypeDir, hmName)
+ if err != nil {
+ if strings.Contains(err.Error(), "no such resource") {
+ diags.AddError("Could not read directory mapping", err.Error())
+ }
+
+ return false, diags
+ }
+
+ hm.importFromAPI(ctx, data)
+
+ return true, nil
+}
+
+// readBack reads information about a created or modified directory mapping from the Proxmox VE API then updates the
+// response state accordingly.
+// The Terraform resource identifier must have been set in the state before this method is called!
+func (r *dirResource) readBack(ctx context.Context, hm *modelDir, respDiags *diag.Diagnostics, respState *tfsdk.State) {
+ found, diags := r.read(ctx, hm)
+
+ respDiags.Append(diags...)
+
+ if !found {
+ respDiags.AddError(
+ "directory mapping resource not found after update",
+ "Failed to find the resource when trying to read back the updated directory mapping's data.",
+ )
+ }
+
+ if !respDiags.HasError() {
+ respDiags.Append(respState.Set(ctx, *hm)...)
+ }
+}
+
+// Configure adds the provider-configured client to the resource.
+func (r *dirResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ cfg, ok := req.ProviderData.(config.Resource)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = cfg.Client.Cluster().HardwareMapping()
+}
+
+// Create creates a new directory mapping.
+func (r *dirResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var hm modelDir
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &hm)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ hmName := hm.Name.ValueString()
+ // Ensure to keep both in sync since the name represents the ID.
+ hm.ID = hm.Name
+
+ apiReq := hm.toCreateRequest()
+
+ if err := r.client.Create(ctx, proxmoxtypes.TypeDir, apiReq); err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Could not create directory mapping %q.", hmName),
+ err.Error(),
+ )
+
+ return
+ }
+
+ r.readBack(ctx, &hm, &resp.Diagnostics, &resp.State)
+}
+
+// Delete deletes an existing directory mapping.
+func (r *dirResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var hm modelDir
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &hm)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ hmID := hm.Name.ValueString()
+
+ if err := r.client.Delete(ctx, proxmoxtypes.TypeDir, hmID); err != nil {
+ if strings.Contains(err.Error(), "no such resource") {
+ resp.Diagnostics.AddWarning(
+ "directory mapping does not exist",
+ fmt.Sprintf(
+ "Could not delete directory mapping %q, it does not exist or has been deleted outside of Terraform.",
+ hmID,
+ ),
+ )
+ } else {
+ resp.Diagnostics.AddError(fmt.Sprintf("Could not delete directory mapping %q.", hmID), err.Error())
+ }
+ }
+}
+
+// ImportState imports a directory mapping from the Proxmox VE API.
+func (r *dirResource) ImportState(
+ ctx context.Context,
+ req resource.ImportStateRequest,
+ resp *resource.ImportStateResponse,
+) {
+ data := modelDir{
+ ID: types.StringValue(req.ID),
+ Name: types.StringValue(req.ID),
+ }
+
+ resource.ImportStatePassthroughID(ctx, path.Root(schemaAttrNameTerraformID), req, resp)
+ r.readBack(ctx, &data, &resp.Diagnostics, &resp.State)
+}
+
+// Metadata defines the name of the directory mapping.
+func (r *dirResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_hardware_mapping_dir"
+}
+
+// Read reads the directory mapping.
+//
+
+func (r *dirResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data modelDir
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ found, diags := r.read(ctx, &data)
+ resp.Diagnostics.Append(diags...)
+
+ if !resp.Diagnostics.HasError() {
+ if found {
+ resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
+ } else {
+ resp.State.RemoveResource(ctx)
+ }
+ }
+}
+
+// Schema defines the schema for the directory mapping.
+func (r *dirResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ comment := resourceSchemaBaseAttrComment
+ comment.Description = "The comment of this directory mapping."
+
+ resp.Schema = schema.Schema{
+ Description: "Manages a directory mapping in a Proxmox VE cluster.",
+ Attributes: map[string]schema.Attribute{
+ schemaAttrNameComment: comment,
+ schemaAttrNameMap: schema.SetNestedAttribute{
+ Description: "The actual map of devices for the hardware mapping.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ schemaAttrNameMapNode: schema.StringAttribute{
+ Description: "The node this mapping applies to.",
+ Required: true,
+ },
+ schemaAttrNameMapPath: schema.StringAttribute{
+ CustomType: customtypes.PathType{},
+ Description: "The path of the map. For directory mappings the path is required and refers" +
+ " to the POSIX path of the directory as visible from the node.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ customtypes.PathDirValueRegEx,
+ ErrResourceMessageInvalidPath(proxmoxtypes.TypeDir),
+ ),
+ },
+ },
+ },
+ },
+ Required: true,
+ Validators: []validator.Set{
+ setvalidator.SizeAtLeast(1),
+ },
+ },
+ schemaAttrNameName: schema.StringAttribute{
+ Description: "The name of this directory mapping.",
+ Required: true,
+ },
+ schemaAttrNameTerraformID: attribute.ResourceID(
+ "The unique identifier of this directory mapping resource.",
+ ),
+ },
+ }
+}
+
+// Update updates an existing directory mapping.
+func (r *dirResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var hmCurrent, hmPlan modelDir
+
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &hmPlan)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &hmCurrent)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ hmName := hmPlan.Name.ValueString()
+
+ apiReq := hmPlan.toUpdateRequest(&hmCurrent)
+
+ if err := r.client.Update(ctx, proxmoxtypes.TypeDir, hmName, apiReq); err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Could not update directory mapping %q.", hmName),
+ err.Error(),
+ )
+
+ return
+ }
+
+ r.readBack(ctx, &hmPlan, &resp.Diagnostics, &resp.State)
+}
+
+// NewDirResource returns a new resource for managing a directory mapping.
+// This is a helper function to simplify the provider implementation.
+func NewDirResource() resource.Resource {
+ return &dirResource{}
+}
diff --git a/fwprovider/nodes/hardwaremapping/resource_hardware_mapping_test.go b/fwprovider/nodes/hardwaremapping/resource_hardware_mapping_test.go
index 45aab933..c7438be4 100644
--- a/fwprovider/nodes/hardwaremapping/resource_hardware_mapping_test.go
+++ b/fwprovider/nodes/hardwaremapping/resource_hardware_mapping_test.go
@@ -27,15 +27,18 @@ import (
)
const (
+ accTestHardwareMappingNameDir = "proxmox_virtual_environment_hardware_mapping_dir.test"
accTestHardwareMappingNamePCI = "proxmox_virtual_environment_hardware_mapping_pci.test"
accTestHardwareMappingNameUSB = "proxmox_virtual_environment_hardware_mapping_usb.test"
)
type accTestHardwareMappingFakeData struct {
- Comments []string `fake:"{sentence:3}" fakesize:"2"`
- MapComments []string `fake:"{sentence:3}" fakesize:"2"`
- MapDeviceIDs []string `fake:"{linuxdeviceid}" fakesize:"2"`
- MapIOMMUGroups []uint `fake:"{number:1,20}" fakesize:"2"`
+ Comments []string `fake:"{sentence:3}" fakesize:"2"`
+ MapComments []string `fake:"{sentence:3}" fakesize:"2"`
+ MapDeviceIDs []string `fake:"{linuxdeviceid}" fakesize:"2"`
+ MapIOMMUGroups []uint `fake:"{number:1,20}" fakesize:"2"`
+ // These paths must exist on the host system, use a hardcoded list
+ MapPathsDir []string `fake:"{randomstring:[/home,/root,/mnt,/tmp]}" fakesize:"2"`
MapPathsPCI []string `fake:"{linuxdevicepathpci}" fakesize:"2"`
MapPathsUSB []string `fake:"{linuxdevicepathusb}" fakesize:"2"`
MapSubsystemIDs []string `fake:"{linuxdeviceid}" fakesize:"2"`
@@ -94,6 +97,226 @@ func testAccResourceHardwareMappingInit(t *testing.T) (*accTestHardwareMappingFa
return &data, te
}
+// TestAccResourceHardwareMappingDirValidInput runs tests for directory mapping resource definitions with valid input
+// where all possible attributes are
+// specified.
+// All implementations of the [github.com/hashicorp/terraform-plugin-framework/resource.Resource] interface are tested
+// in sequential steps.
+func TestAccResourceHardwareMappingDirValidInput(t *testing.T) {
+ data, te := testAccResourceHardwareMappingInit(t)
+
+ resource.Test(
+ t, resource.TestCase{
+ ProtoV6ProviderFactories: te.AccProviders,
+ Steps: []resource.TestStep{
+ // Test the "Create" and "Read" implementations where all possible attributes are specified.
+ {
+ Config: fmt.Sprintf(
+ `
+ resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
+ comment = "%s"
+ name = "%s"
+ map = [
+ {
+ node = "%s"
+ path = "%s"
+ },
+ ]
+ }
+ `,
+ data.Comments[0],
+ data.Names[0],
+ te.NodeName,
+ data.MapPathsDir[0],
+ ),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "comment", data.Comments[0]),
+ resource.TestCheckResourceAttrSet(accTestHardwareMappingNameDir, "id"),
+ resource.TestCheckTypeSetElemNestedAttrs(
+ accTestHardwareMappingNameDir, "map.*", map[string]string{
+ "node": te.NodeName,
+ "path": data.MapPathsDir[0],
+ },
+ ),
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "name", data.Names[0]),
+ ),
+ },
+
+ // Test the "ImportState" implementation and ensure that PCI-only attributes are not set.
+ {
+ ImportState: true,
+ ImportStateId: data.Names[0],
+ ImportStateVerify: true,
+ ResourceName: accTestHardwareMappingNameDir,
+ },
+
+ // Test the "Update" implementation where all possible attributes are specified.
+ {
+ Config: fmt.Sprintf(
+ `
+ resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
+ comment = "%s"
+ name = "%s"
+ map = [
+ {
+ node = "%s"
+ path = "%s"
+ },
+ ]
+ }
+ `,
+ data.Comments[1],
+ data.Names[0],
+ te.NodeName,
+ data.MapPathsDir[1],
+ ),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "comment", data.Comments[1]),
+ resource.TestCheckResourceAttrSet(accTestHardwareMappingNameDir, "id"),
+ resource.TestCheckTypeSetElemNestedAttrs(
+ accTestHardwareMappingNameDir, "map.*", map[string]string{
+ "node": te.NodeName,
+ "path": data.MapPathsDir[1],
+ },
+ ),
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "name", data.Names[0]),
+ ),
+ },
+ },
+ },
+ )
+}
+
+// TestAccResourceHardwareMappingDirValidInputMinimal runs tests for directory mapping resource definitions with
+// valid input that only have the minimum
+// amount of attributes set to test computed and default values within the resulting plan and state. The last step sets
+// the undefined values to test the update
+// logic.
+// All implementations of the [github.com/hashicorp/terraform-plugin-framework/resource.Resource] interface are tested
+// in sequential steps.
+func TestAccResourceHardwareMappingDirValidInputMinimal(t *testing.T) {
+ data, te := testAccResourceHardwareMappingInit(t)
+
+ resource.Test(
+ t, resource.TestCase{
+ ProtoV6ProviderFactories: te.AccProviders,
+ Steps: []resource.TestStep{
+ // Test the "Create" and "Read" implementations with only the minimum amount of attributes being set.
+ {
+ Config: fmt.Sprintf(
+ `
+ resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
+ name = "%s"
+ map = [
+ {
+ node = "%s"
+ path = "%s"
+ },
+ ]
+ }
+ `,
+ data.Names[0],
+ te.NodeName,
+ data.MapPathsDir[0],
+ ),
+ ConfigStateChecks: []statecheck.StateCheck{
+ // Optional attributes should all be unset.
+ statecheck.ExpectKnownValue(accTestHardwareMappingNameDir,
+ tfjsonpath.New("comment"),
+ knownvalue.Null()),
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrSet(accTestHardwareMappingNameDir, "id"),
+ resource.TestCheckTypeSetElemNestedAttrs(
+ accTestHardwareMappingNameDir, "map.*", map[string]string{
+ "node": te.NodeName,
+ "path": data.MapPathsDir[0],
+ },
+ ),
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "name", data.Names[0]),
+ ),
+ },
+
+ // Test the "Update" implementation by setting all previously undefined attributes.
+ {
+ Config: fmt.Sprintf(
+ `
+ resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
+ comment = "%s"
+ name = "%s"
+ map = [
+ {
+ node = "%s"
+ path = "%s"
+ },
+ ]
+ }
+ `,
+ data.Comments[0],
+ data.Names[0],
+ te.NodeName,
+ data.MapPathsDir[0],
+ ),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "comment", data.Comments[0]),
+ resource.TestCheckResourceAttrSet(accTestHardwareMappingNameDir, "id"),
+ resource.TestCheckTypeSetElemNestedAttrs(
+ accTestHardwareMappingNameDir, "map.*", map[string]string{
+ "node": te.NodeName,
+ "path": data.MapPathsDir[0],
+ },
+ ),
+ resource.TestCheckResourceAttr(accTestHardwareMappingNameDir, "name", data.Names[0]),
+ ),
+ },
+ },
+ },
+ )
+}
+
+// TestAccResourceHardwareMappingDirInvalidInput runs tests for directory mapping resource definitions where all
+// possible attributes are specified.
+// Only the "Create" method implementation of the [github.com/hashicorp/terraform-plugin-framework/resource.Resource]
+// interface is tested in sequential steps.
+func TestAccResourceHardwareMappingDirInvalidInput(t *testing.T) {
+ data, te := testAccResourceHardwareMappingInit(t)
+
+ resource.Test(
+ t, resource.TestCase{
+ ProtoV6ProviderFactories: te.AccProviders,
+ Steps: []resource.TestStep{
+ // Test the "Create" method implementation where all possible attributes are specified, but an error is expected
+ // when using an invalid device path.
+ {
+ Config: fmt.Sprintf(
+ `
+ resource "proxmox_virtual_environment_hardware_mapping_usb" "test" {
+ comment = "%s"
+ name = "%s"
+ map = [
+ {
+ comment = "%s"
+ id = "%s"
+ node = "%s"
+ # Only valid Linux USB device paths should pass the verification.
+ path = "xyz3:1337foobar"
+ },
+ ]
+ }
+ `,
+ data.Comments[0],
+ data.Names[0],
+ data.Comments[1],
+ data.MapDeviceIDs[0],
+ te.NodeName,
+ ),
+ ExpectError: regexp.MustCompile(`valid Linux device path for hardware mapping of type "usb"`),
+ },
+ },
+ },
+ )
+}
+
// TestAccResourceHardwareMappingPCIValidInput runs tests for PCI hardware mapping resource definitions with valid input
// where all possible attributes are
// specified.
diff --git a/fwprovider/provider.go b/fwprovider/provider.go
index b0ae8345..d6793321 100644
--- a/fwprovider/provider.go
+++ b/fwprovider/provider.go
@@ -506,6 +506,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
apt.NewStandardRepositoryResource,
ha.NewHAGroupResource,
ha.NewHAResourceResource,
+ hardwaremapping.NewDirResource,
hardwaremapping.NewPCIResource,
hardwaremapping.NewUSBResource,
metrics.NewMetricsServerResource,
@@ -532,6 +533,7 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
ha.NewHAResourceDataSource,
ha.NewHAResourcesDataSource,
hardwaremapping.NewDataSource,
+ hardwaremapping.NewDirDataSource,
hardwaremapping.NewPCIDataSource,
hardwaremapping.NewUSBDataSource,
metrics.NewMetricsServerDatasource,
diff --git a/fwprovider/test/resource_vm_test.go b/fwprovider/test/resource_vm_test.go
index ce610878..210bda4b 100644
--- a/fwprovider/test/resource_vm_test.go
+++ b/fwprovider/test/resource_vm_test.go
@@ -9,9 +9,11 @@
package test
import (
+ "fmt"
"regexp"
"testing"
+ "github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
@@ -22,6 +24,10 @@ func TestAccResourceVM(t *testing.T) {
t.Parallel()
te := InitEnvironment(t)
+ dirName := fmt.Sprintf("dir_%s", gofakeit.Word())
+ te.AddTemplateVars(map[string]interface{}{
+ "DirName": dirName,
+ })
tests := []struct {
name string
@@ -396,51 +402,50 @@ func TestAccResourceVM(t *testing.T) {
),
},
}},
- // Depends on #1902
- // {"create virtiofs block", []resource.TestStep{
- // {
- // Config: te.RenderConfig(`
- // resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
- // name = "test"
+ {"create virtiofs block", []resource.TestStep{
+ {
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
+ name = "{{.DirName}}"
- // map {
- // node = "{{.NodeName}}"
- // path = "/mnt"
- // }
- // }`, WithRootUser()),
- // Check: resource.ComposeTestCheckFunc(
- // ResourceAttributes("proxmox_virtual_environment_hardware_mapping_dir.test", map[string]string{
- // "name": "test",
- // "map.0.node": "{{.NodeName}}",
- // "map.0.path": "/mnt",
- // }),
- // ),
- // },
- // {
- // Config: te.RenderConfig(`
- // resource "proxmox_virtual_environment_vm" "test_vm" {
- // node_name = "{{.NodeName}}"
- // started = false
+ map = [{
+ node = "{{.NodeName}}"
+ path = "/mnt"
+ }]
+ }`, WithRootUser()),
+ Check: resource.ComposeTestCheckFunc(
+ ResourceAttributes("proxmox_virtual_environment_hardware_mapping_dir.test", map[string]string{
+ "name": dirName,
+ "map.0.node": te.NodeName,
+ "map.0.path": "/mnt",
+ }),
+ ),
+ },
+ {
+ Config: te.RenderConfig(`
+ resource "proxmox_virtual_environment_vm" "test_vm" {
+ node_name = "{{.NodeName}}"
+ started = false
- // virtiofs {
- // mapping = "test"
- // cache = "always"
- // direct_io = true
- // expose_acl = false
- // expose_xattr = false
- // }
- // }`, WithRootUser()),
- // Check: resource.ComposeTestCheckFunc(
- // ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
- // "virtiofs.0.mapping": "test",
- // "virtiofs.0.cache": "always",
- // "virtiofs.0.direct_io": "true",
- // "virtiofs.0.expose_acl": "false",
- // "virtiofs.0.expose_xattr": "false",
- // }),
- // ),
- // },
- // }},
+ virtiofs {
+ mapping = "{{.DirName}}"
+ cache = "always"
+ direct_io = true
+ expose_acl = false
+ expose_xattr = false
+ }
+ }`, WithRootUser()),
+ Check: resource.ComposeTestCheckFunc(
+ ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
+ "virtiofs.0.mapping": dirName,
+ "virtiofs.0.cache": "always",
+ "virtiofs.0.direct_io": "true",
+ "virtiofs.0.expose_acl": "false",
+ "virtiofs.0.expose_xattr": "false",
+ }),
+ ),
+ },
+ }},
}
for _, tt := range tests {
diff --git a/fwprovider/types/hardwaremapping/path.go b/fwprovider/types/hardwaremapping/path.go
index 284c1ae7..b7bbadd5 100644
--- a/fwprovider/types/hardwaremapping/path.go
+++ b/fwprovider/types/hardwaremapping/path.go
@@ -37,6 +37,9 @@ var ErrValueConversion = func(format string, attrs ...any) error {
}
var (
+ // PathDirValueRegEx is the regular expression for a POSIX path.
+ PathDirValueRegEx = regexp.MustCompile(`^/.+$`)
+
// PathPCIValueRegEx is the regular expression for a PCI hardware mapping path.
PathPCIValueRegEx = regexp.MustCompile(`^[a-f0-9]{4,}:[a-f0-9]{2}:[a-f0-9]{2}(\.[a-f0-9])?$`)
@@ -128,6 +131,8 @@ func (v PathValue) Equal(o attr.Value) bool {
// IsProxmoxType checks whether the value match the given hardware mapping type.
func (v PathValue) IsProxmoxType(hmType proxmoxtypes.Type) bool {
switch hmType {
+ case proxmoxtypes.TypeDir:
+ return PathDirValueRegEx.MatchString(v.ValueString())
case proxmoxtypes.TypePCI:
return PathPCIValueRegEx.MatchString(v.ValueString())
case proxmoxtypes.TypeUSB:
diff --git a/main.go b/main.go
index 579cfc7d..0395120e 100644
--- a/main.go
+++ b/main.go
@@ -42,6 +42,7 @@ import (
//go:generate cp ./build/docs-gen/data-sources/virtual_environment_datastores.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_dir.md ./docs/data-sources/
//go:generate cp ./build/docs-gen/data-sources/virtual_environment_hardware_mapping_pci.md ./docs/data-sources/
//go:generate cp ./build/docs-gen/data-sources/virtual_environment_hardware_mapping_usb.md ./docs/data-sources/
//go:generate cp ./build/docs-gen/data-sources/virtual_environment_hardware_mappings.md ./docs/data-sources/
@@ -58,6 +59,7 @@ import (
//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_dir.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/
diff --git a/proxmox/cluster/mapping/types.go b/proxmox/cluster/mapping/types.go
index 39c250cb..c3f41eb6 100644
--- a/proxmox/cluster/mapping/types.go
+++ b/proxmox/cluster/mapping/types.go
@@ -70,11 +70,11 @@ type GetResponseData struct {
type ListResponseData struct {
DataBase
- // ChecksPCI might contain relevant diagnostics about incorrect [typesHWM.TypePCI] configurations.
+ // Checks might contain relevant diagnostics about incorrect [typesHWM.TypePCI] configurations.
// The name of the node must be passed to the Proxmox VE API call which maps to the "check-node" URL parameter.
// Note that the Proxmox VE API, for whatever reason, only returns one error at a time, even though the field is an
// array.
- ChecksPCI []NodeCheckDiag `json:"checks,omitempty"`
+ Checks []NodeCheckDiag `json:"checks,omitempty"`
// ChecksUSB might contain relevant diagnostics about incorrect [typesHWM.TypeUSB] configurations.
// The name of the node must be passed to the Proxmox VE API call which maps to the "check-node" URL parameter.
diff --git a/proxmox/types/hardwaremapping/map.go b/proxmox/types/hardwaremapping/map.go
index 808c3460..a43bd789 100644
--- a/proxmox/types/hardwaremapping/map.go
+++ b/proxmox/types/hardwaremapping/map.go
@@ -116,11 +116,14 @@ func (hm Map) String() string {
return fmt.Sprintf("%s%s%s", k, string(attrValueSeparator), v)
}
attrs := make([]string, 0, attrCountMax)
- attrs = append(
- attrs,
- joinKV(attrNameDeviceID, hm.ID.String()),
- joinKV(attrNameNode, hm.Node),
- )
+
+ // ID is optional for directory mappings
+ if hm.ID != "" {
+ attrs = append(attrs, joinKV(attrNameDeviceID, hm.ID.String()))
+ }
+
+ // Node is common among all mappings
+ attrs = append(attrs, joinKV(attrNameNode, hm.Node))
if hm.Path != nil {
attrs = append(attrs, joinKV(attrNamePath, *hm.Path))
diff --git a/proxmox/types/hardwaremapping/type.go b/proxmox/types/hardwaremapping/type.go
index 6a7eafa1..92c00ff8 100644
--- a/proxmox/types/hardwaremapping/type.go
+++ b/proxmox/types/hardwaremapping/type.go
@@ -12,11 +12,15 @@ import (
//nolint:gochecknoglobals
var (
+ // TypeDir is an identifier for a directory mapping type.
+ // Do not modify this package-global variable as it acts as a safer variant compared to "iota" based constants!
+ TypeDir = Type{"dir"}
+
// TypePCI is an identifier for a PCI hardware mapping type.
// Do not modify this package-global variable as it acts as a safer variant compared to "iota" based constants!
TypePCI = Type{"pci"}
- // TypeUSB is an identifier for a PCI hardware mapping type.
+ // TypeUSB is an identifier for a USB hardware mapping type.
// Do not modify this package-global variable as it acts as a safer variant compared to "iota" based constants!
TypeUSB = Type{"usb"}
)
@@ -81,6 +85,8 @@ func (t *Type) UnmarshalJSON(b []byte) error {
// An error is returned if the input string does not match any known type.
func ParseType(input string) (Type, error) {
switch input {
+ case TypeDir.String():
+ return TypeDir, nil
case TypePCI.String():
return TypePCI, nil
case TypeUSB.String():