mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
feat(vm): implement filtering in vms data source. (#1423)
* feat(vm): implement filtering in vms data source. * Additional attributes for vm data source (status, template) * fix qodana CI job condition --------- Signed-off-by: Konstantin Kornienko <konstantin.kornienko@gmail.com> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
91a16af747
commit
65f8ba5bfe
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@ -10,7 +10,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
qodana:
|
qodana:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'bpg'
|
if: github.event.pull_request.head.repo.owner.login == 'bpg' || github.event.ref == 'refs/heads/main'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
@ -27,3 +27,5 @@ data "proxmox_virtual_environment_vm" "test_vm" {
|
|||||||
|
|
||||||
- `name` - The virtual machine name.
|
- `name` - The virtual machine name.
|
||||||
- `tags` - A list of tags of the VM.
|
- `tags` - A list of tags of the VM.
|
||||||
|
- `status` - Status of the VM
|
||||||
|
- `template` - Is VM a template (true) or a regular VM (false)
|
||||||
|
@ -15,13 +15,42 @@ Retrieves information about all VMs in the Proxmox cluster.
|
|||||||
data "proxmox_virtual_environment_vms" "ubuntu_vms" {
|
data "proxmox_virtual_environment_vms" "ubuntu_vms" {
|
||||||
tags = ["ubuntu"]
|
tags = ["ubuntu"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_vms" "ubuntu_templates" {
|
||||||
|
tags = ["template", "latest"]
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "template"
|
||||||
|
values = [true]
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "status"
|
||||||
|
values = ["stopped"]
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "name"
|
||||||
|
regex = true
|
||||||
|
values = ["^ubuntu-20.*$"]
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "node_name"
|
||||||
|
regex = true
|
||||||
|
values = ["node_us_[1-3]", "node_eu_[1-3]"]
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Argument Reference
|
## Argument Reference
|
||||||
|
|
||||||
- `node_name` - (Optional) The node name.
|
- `node_name` - (Optional) The node name. All cluster nodes will be queried in case this is omitted
|
||||||
- `tags` - (Optional) A list of tags to filter the VMs. The VM must have all
|
- `tags` - (Optional) A list of tags to filter the VMs. The VM must have all
|
||||||
the tags to be included in the result.
|
the tags to be included in the result.
|
||||||
|
- `filter` - (Optional) Filter blocks. The VM must satisfy all filter blocks to be included in the result.
|
||||||
|
- `name` - Name of the VM attribute to filter on. One of [`name`, `template`, `status`, `node_name`]
|
||||||
|
- `values` - List of values to pass the filter. VM's attribute should match at least one value in the list.
|
||||||
|
|
||||||
## Attribute Reference
|
## Attribute Reference
|
||||||
|
|
||||||
@ -30,3 +59,5 @@ data "proxmox_virtual_environment_vms" "ubuntu_vms" {
|
|||||||
- `node_name` - The node name.
|
- `node_name` - The node name.
|
||||||
- `tags` - A list of tags of the VM.
|
- `tags` - A list of tags of the VM.
|
||||||
- `vm_id` - The VM identifier.
|
- `vm_id` - The VM identifier.
|
||||||
|
- `status` - Status of the VM
|
||||||
|
- `template` - Is VM a template (true) or a regular VM (false)
|
||||||
|
@ -10,6 +10,31 @@ data "proxmox_virtual_environment_vms" "example" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_vms" "template_example" {
|
||||||
|
depends_on = [proxmox_virtual_environment_vm.example]
|
||||||
|
tags = ["ubuntu"]
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "template"
|
||||||
|
values = [false]
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "status"
|
||||||
|
values = ["running"]
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {
|
||||||
|
name = "name"
|
||||||
|
regex = true
|
||||||
|
values = [".*ubuntu.*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
output "proxmox_virtual_environment_vms_example" {
|
output "proxmox_virtual_environment_vms_example" {
|
||||||
value = data.proxmox_virtual_environment_vms.example.vms
|
value = data.proxmox_virtual_environment_vms.example.vms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "proxmox_virtual_environment_template_vms_example" {
|
||||||
|
value = data.proxmox_virtual_environment_vms.template_example.vms
|
||||||
|
}
|
||||||
|
@ -354,9 +354,11 @@ type ListResponseBody struct {
|
|||||||
|
|
||||||
// ListResponseData contains the data from an virtual machine list response.
|
// ListResponseData contains the data from an virtual machine list response.
|
||||||
type ListResponseData struct {
|
type ListResponseData struct {
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
Tags *string `json:"tags,omitempty"`
|
Tags *string `json:"tags,omitempty"`
|
||||||
VMID int `json:"vmid,omitempty"`
|
Template *types.CustomBool `json:"template,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"`
|
||||||
|
VMID int `json:"vmid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MigrateRequestBody contains the body for a VM migration request.
|
// MigrateRequestBody contains the body for a VM migration request.
|
||||||
|
@ -24,6 +24,8 @@ const (
|
|||||||
mkDataSourceVirtualEnvironmentVMName = "name"
|
mkDataSourceVirtualEnvironmentVMName = "name"
|
||||||
mkDataSourceVirtualEnvironmentVMNodeName = "node_name"
|
mkDataSourceVirtualEnvironmentVMNodeName = "node_name"
|
||||||
mkDataSourceVirtualEnvironmentVMTags = "tags"
|
mkDataSourceVirtualEnvironmentVMTags = "tags"
|
||||||
|
mkDataSourceVirtualEnvironmentVMTemplate = "template"
|
||||||
|
mkDataSourceVirtualEnvironmentVMStatus = "status"
|
||||||
mkDataSourceVirtualEnvironmentVMVMID = "vm_id"
|
mkDataSourceVirtualEnvironmentVMVMID = "vm_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,6 +49,16 @@ func VM() *schema.Resource {
|
|||||||
Computed: true,
|
Computed: true,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
},
|
},
|
||||||
|
mkDataSourceVirtualEnvironmentVMTemplate: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Is VM a template (true) or a regular VM (false)",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
mkDataSourceVirtualEnvironmentVMStatus: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "Status of the VM",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
mkDataSourceVirtualEnvironmentVMVMID: {
|
mkDataSourceVirtualEnvironmentVMVMID: {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Description: "The VM identifier",
|
Description: "The VM identifier",
|
||||||
@ -82,6 +94,11 @@ func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia
|
|||||||
return diag.FromErr(err)
|
return diag.FromErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vmConfig, err := client.Node(nodeName).VM(vmID).GetVM(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
if vmStatus.Name != nil {
|
if vmStatus.Name != nil {
|
||||||
err = d.Set(mkDataSourceVirtualEnvironmentVMName, *vmStatus.Name)
|
err = d.Set(mkDataSourceVirtualEnvironmentVMName, *vmStatus.Name)
|
||||||
} else {
|
} else {
|
||||||
@ -103,6 +120,17 @@ func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia
|
|||||||
sort.Strings(tags)
|
sort.Strings(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = d.Set(mkDataSourceVirtualEnvironmentVMStatus, vmStatus.Status)
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
|
||||||
|
if vmConfig.Template == nil {
|
||||||
|
err = d.Set(mkDataSourceVirtualEnvironmentVMTemplate, false)
|
||||||
|
} else {
|
||||||
|
err = d.Set(mkDataSourceVirtualEnvironmentVMTemplate, *vmConfig.Template)
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
|
||||||
err = d.Set(mkDataSourceVirtualEnvironmentVMTags, tags)
|
err = d.Set(mkDataSourceVirtualEnvironmentVMTags, tags)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ func TestVMSchema(t *testing.T) {
|
|||||||
mkDataSourceVirtualEnvironmentVMName: schema.TypeString,
|
mkDataSourceVirtualEnvironmentVMName: schema.TypeString,
|
||||||
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
|
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
|
||||||
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
||||||
|
mkDataSourceVirtualEnvironmentVMTemplate: schema.TypeBool,
|
||||||
|
mkDataSourceVirtualEnvironmentVMStatus: schema.TypeString,
|
||||||
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
|
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ package datasource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -18,11 +20,16 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mkDataSourceVirtualEnvironmentVMs = "vms"
|
mkDataSourceVirtualEnvironmentVMs = "vms"
|
||||||
|
mkDataSourceFilter = "filter"
|
||||||
|
mkDataSourceFilterName = "name"
|
||||||
|
mkDataSourceFilterValues = "values"
|
||||||
|
mkDataSourceFilterRegex = "regex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VMs returns a resource for the Proxmox VMs.
|
// VMs returns a resource for the Proxmox VMs.
|
||||||
@ -32,7 +39,7 @@ func VMs() *schema.Resource {
|
|||||||
mkDataSourceVirtualEnvironmentVMNodeName: {
|
mkDataSourceVirtualEnvironmentVMNodeName: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The node name",
|
Description: "The node name. All cluster nodes will be queried in case this is omitted",
|
||||||
},
|
},
|
||||||
mkDataSourceVirtualEnvironmentVMTags: {
|
mkDataSourceVirtualEnvironmentVMTags: {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
@ -40,6 +47,32 @@ func VMs() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
},
|
},
|
||||||
|
mkDataSourceFilter: {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Filter blocks",
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
mkDataSourceFilterName: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "Attribute to filter on. One of [name, template, status, node_name]",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
mkDataSourceFilterValues: {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Description: "List of values to pass the filter (OR logic)",
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
mkDataSourceFilterRegex: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
Description: "Treat values as regex patterns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
mkDataSourceVirtualEnvironmentVMs: {
|
mkDataSourceVirtualEnvironmentVMs: {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Description: "VMs",
|
Description: "VMs",
|
||||||
@ -81,6 +114,8 @@ func vmsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Di
|
|||||||
|
|
||||||
sort.Strings(filterTags)
|
sort.Strings(filterTags)
|
||||||
|
|
||||||
|
filters := d.Get(mkDataSourceFilter).([]interface{})
|
||||||
|
|
||||||
var vms []interface{}
|
var vms []interface{}
|
||||||
|
|
||||||
for _, nodeName := range nodeNames {
|
for _, nodeName := range nodeNames {
|
||||||
@ -127,6 +162,23 @@ func vmsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Di
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.Template != (*types.CustomBool)(nil) && *data.Template {
|
||||||
|
vm[mkDataSourceVirtualEnvironmentVMTemplate] = true
|
||||||
|
} else {
|
||||||
|
vm[mkDataSourceVirtualEnvironmentVMTemplate] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
vm[mkDataSourceVirtualEnvironmentVMStatus] = *data.Status
|
||||||
|
|
||||||
|
if len(filters) > 0 {
|
||||||
|
allFiltersMatched, err := checkVMMatchFilters(vm, filters)
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
|
||||||
|
if !allFiltersMatched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vms = append(vms, vm)
|
vms = append(vms, vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,3 +212,55 @@ func getNodeNames(ctx context.Context, d *schema.ResourceData, api proxmox.Clien
|
|||||||
|
|
||||||
return nodeNames, nil
|
return nodeNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkVMMatchFilters(vm map[string]interface{}, filters []interface{}) (bool, error) {
|
||||||
|
for _, v := range filters {
|
||||||
|
filter := v.(map[string]interface{})
|
||||||
|
filterName := filter["name"]
|
||||||
|
filterValues := filter["values"].([]interface{})
|
||||||
|
filterRegex := filter["regex"].(bool)
|
||||||
|
|
||||||
|
var vmValue string
|
||||||
|
|
||||||
|
switch filterName {
|
||||||
|
case "template":
|
||||||
|
vmValue = strconv.FormatBool(vm[mkDataSourceVirtualEnvironmentVMTemplate].(bool))
|
||||||
|
case "status":
|
||||||
|
vmValue = vm[mkDataSourceVirtualEnvironmentVMStatus].(string)
|
||||||
|
case "name":
|
||||||
|
vmValue = vm[mkDataSourceVirtualEnvironmentVMName].(string)
|
||||||
|
case "node_name":
|
||||||
|
vmValue = vm[mkDataSourceVirtualEnvironmentVMNodeName].(string)
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf(
|
||||||
|
"unknown filter name '%s', should be one of [name, template, status, node_name]",
|
||||||
|
filterName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
atLeastOneValueMatched := false
|
||||||
|
|
||||||
|
for _, filterValue := range filterValues {
|
||||||
|
if filterRegex {
|
||||||
|
r, err := regexp.Compile(filterValue.(string))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error interpreting filter '%s' value '%s' as regex: %w", filterName, filterValue, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.MatchString(vmValue) {
|
||||||
|
atLeastOneValueMatched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if filterValue == vmValue {
|
||||||
|
atLeastOneValueMatched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !atLeastOneValueMatched {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ func TestVMsSchema(t *testing.T) {
|
|||||||
test.AssertValueTypes(t, s, map[string]schema.ValueType{
|
test.AssertValueTypes(t, s, map[string]schema.ValueType{
|
||||||
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
|
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
|
||||||
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
||||||
|
mkDataSourceFilter: schema.TypeList,
|
||||||
mkDataSourceVirtualEnvironmentVMs: schema.TypeList,
|
mkDataSourceVirtualEnvironmentVMs: schema.TypeList,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,4 +55,11 @@ func TestVMsSchema(t *testing.T) {
|
|||||||
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
|
||||||
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
|
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
filterSchema := test.AssertNestedSchemaExistence(t, s, mkDataSourceFilter)
|
||||||
|
test.AssertValueTypes(t, filterSchema, map[string]schema.ValueType{
|
||||||
|
mkDataSourceFilterName: schema.TypeString,
|
||||||
|
mkDataSourceFilterValues: schema.TypeList,
|
||||||
|
mkDataSourceFilterRegex: schema.TypeBool,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user