0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21: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:
Konstantin Kornienko 2024-07-05 22:52:39 +03:00 committed by GitHub
parent 91a16af747
commit 65f8ba5bfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 208 additions and 6 deletions

View File

@ -10,7 +10,7 @@ on:
jobs:
qodana:
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:
contents: write
pull-requests: write

View File

@ -27,3 +27,5 @@ data "proxmox_virtual_environment_vm" "test_vm" {
- `name` - The virtual machine name.
- `tags` - A list of tags of the VM.
- `status` - Status of the VM
- `template` - Is VM a template (true) or a regular VM (false)

View File

@ -15,13 +15,42 @@ Retrieves information about all VMs in the Proxmox cluster.
data "proxmox_virtual_environment_vms" "ubuntu_vms" {
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
- `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
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
@ -30,3 +59,5 @@ data "proxmox_virtual_environment_vms" "ubuntu_vms" {
- `node_name` - The node name.
- `tags` - A list of tags of the VM.
- `vm_id` - The VM identifier.
- `status` - Status of the VM
- `template` - Is VM a template (true) or a regular VM (false)

View File

@ -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" {
value = data.proxmox_virtual_environment_vms.example.vms
}
output "proxmox_virtual_environment_template_vms_example" {
value = data.proxmox_virtual_environment_vms.template_example.vms
}

View File

@ -354,9 +354,11 @@ type ListResponseBody struct {
// ListResponseData contains the data from an virtual machine list response.
type ListResponseData struct {
Name *string `json:"name,omitempty"`
Tags *string `json:"tags,omitempty"`
VMID int `json:"vmid,omitempty"`
Name *string `json:"name,omitempty"`
Tags *string `json:"tags,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.

View File

@ -24,6 +24,8 @@ const (
mkDataSourceVirtualEnvironmentVMName = "name"
mkDataSourceVirtualEnvironmentVMNodeName = "node_name"
mkDataSourceVirtualEnvironmentVMTags = "tags"
mkDataSourceVirtualEnvironmentVMTemplate = "template"
mkDataSourceVirtualEnvironmentVMStatus = "status"
mkDataSourceVirtualEnvironmentVMVMID = "vm_id"
)
@ -47,6 +49,16 @@ func VM() *schema.Resource {
Computed: true,
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: {
Type: schema.TypeInt,
Description: "The VM identifier",
@ -82,6 +94,11 @@ func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia
return diag.FromErr(err)
}
vmConfig, err := client.Node(nodeName).VM(vmID).GetVM(ctx)
if err != nil {
return diag.FromErr(err)
}
if vmStatus.Name != nil {
err = d.Set(mkDataSourceVirtualEnvironmentVMName, *vmStatus.Name)
} else {
@ -103,6 +120,17 @@ func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Dia
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)
diags = append(diags, diag.FromErr(err)...)

View File

@ -40,6 +40,8 @@ func TestVMSchema(t *testing.T) {
mkDataSourceVirtualEnvironmentVMName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
mkDataSourceVirtualEnvironmentVMTemplate: schema.TypeBool,
mkDataSourceVirtualEnvironmentVMStatus: schema.TypeString,
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
})
}

View File

@ -9,7 +9,9 @@ package datasource
import (
"context"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/google/uuid"
@ -18,11 +20,16 @@ import (
"golang.org/x/exp/slices"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
)
const (
mkDataSourceVirtualEnvironmentVMs = "vms"
mkDataSourceFilter = "filter"
mkDataSourceFilterName = "name"
mkDataSourceFilterValues = "values"
mkDataSourceFilterRegex = "regex"
)
// VMs returns a resource for the Proxmox VMs.
@ -32,7 +39,7 @@ func VMs() *schema.Resource {
mkDataSourceVirtualEnvironmentVMNodeName: {
Type: schema.TypeString,
Optional: true,
Description: "The node name",
Description: "The node name. All cluster nodes will be queried in case this is omitted",
},
mkDataSourceVirtualEnvironmentVMTags: {
Type: schema.TypeList,
@ -40,6 +47,32 @@ func VMs() *schema.Resource {
Optional: true,
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: {
Type: schema.TypeList,
Description: "VMs",
@ -81,6 +114,8 @@ func vmsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Di
sort.Strings(filterTags)
filters := d.Get(mkDataSourceFilter).([]interface{})
var vms []interface{}
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)
}
}
@ -160,3 +212,55 @@ func getNodeNames(ctx context.Context, d *schema.ResourceData, api proxmox.Clien
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
}

View File

@ -38,6 +38,7 @@ func TestVMsSchema(t *testing.T) {
test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
mkDataSourceFilter: schema.TypeList,
mkDataSourceVirtualEnvironmentVMs: schema.TypeList,
})
@ -54,4 +55,11 @@ func TestVMsSchema(t *testing.T) {
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
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,
})
}