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:
parent
91a16af747
commit
65f8ba5bfe
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)...)
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user