0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-03 03:52:58 +00:00

feat(vm): Add bare minimum VM datasource (#268)

* feat(vm): Add a bare minimum VM datasource

* fix linter errors

* fix doc

* add lookup across all nodes in the cluster, add filter by tags

* stable vm list

* fix linter errors

* pr feedback: switch to dynamic id for vms

* add datasource examples

* add unit tests

* Update virtual_environment_vms.md
This commit is contained in:
Pavel Boldyrev 2023-03-21 21:52:58 -04:00 committed by GitHub
parent 7a0e1db6c4
commit c2d3f46474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 471 additions and 4 deletions

View File

@ -11,6 +11,6 @@ plugins:
remote_theme: pmarsceill/just-the-docs
# Theme settings.
footer_content: 'Copyright &copy; 2019-2021 <a href="https://danitso.com/" target="_blank">Danitso</a> - Distributed under the <a href="https://github.com/danitso/terraform-provider-proxmox/blob/master/LICENSE" target="_blank">Mozilla Public License 2.0</a><br>Copyright &copy; 2021-2022 <a href="https://github.com/bpg/" target="_blank">Pavel Boldyrev</a> - Distributed under the <a href="https://github.com/bpg/terraform-provider-proxmox/blob/main/LICENSE" target="_blank">Mozilla Public License 2.0</a>'
footer_content: 'Copyright &copy; 2019-2021 <a href="https://danitso.com/" target="_blank">Danitso</a> - Distributed under the <a href="https://github.com/danitso/terraform-provider-proxmox/blob/master/LICENSE" target="_blank">Mozilla Public License 2.0</a><br>Copyright &copy; 2021-2023 <a href="https://github.com/bpg/" target="_blank">Pavel Boldyrev</a> - Distributed under the <a href="https://github.com/bpg/terraform-provider-proxmox/blob/main/LICENSE" target="_blank">Mozilla Public License 2.0</a>'
heading_anchors: true
search_enabled: false

View File

@ -0,0 +1,31 @@
---
layout: page
title: proxmox_virtual_environment_vm
permalink: /data-sources/virtual_environment_vm
nav_order: 17
parent: Data Sources
subcategory: Virtual Environment
---
# Data Source: proxmox_virtual_environment_vm
Retrieves information about a specific VM.
## Example Usage
```terraform
data "proxmox_virtual_environment_vm" "test_vm" {
node_name = "test"
vm_id = 100
}
```
## Argument Reference
- `node_name` - (Required) The node name.
- `vm_id` - (Required) The VM identifier.
## Attribute Reference
- `name` - The virtual machine name.
- `tags` - A list of tags of the VM.

View File

@ -0,0 +1,34 @@
---
layout: page
title: proxmox_virtual_environment_vms
permalink: /data-sources/virtual_environment_vms
nav_order: 18
parent: Data Sources
subcategory: Virtual Environment
---
# Data Source: proxmox_virtual_environment_vms
Retrieves information about all VMs in the Proxmox cluster.
## Example Usage
```terraform
data "proxmox_virtual_environment_vms" "ubuntu_vms" {
tags = ["ubuntu"]
}
```
## Argument Reference
- `node_name` - (Optional) The node name.
- `tags` - (Optional) A list of tags to filter the VMs. The VM must have all
the tags to be included in the result.
## Attribute Reference
- `vms` - The VMs list.
- `name` - The virtual machine name.
- `node_name` - The node name.
- `tags` - A list of tags of the VM.
- `vm_id` - The VM identifier.

View File

@ -0,0 +1,9 @@
data "proxmox_virtual_environment_vm" "example" {
depends_on = [proxmox_virtual_environment_vm.example]
vm_id = proxmox_virtual_environment_vm.example.vm_id
node_name = data.proxmox_virtual_environment_nodes.example.names[0]
}
output "proxmox_virtual_environment_vm_example" {
value = data.proxmox_virtual_environment_vm.example
}

View File

@ -0,0 +1,15 @@
data "proxmox_virtual_environment_vms" "example" {
depends_on = [proxmox_virtual_environment_vm.example]
tags = ["ubuntu"]
lifecycle {
postcondition {
condition = length(self.vms) == 1
error_message = "Only 1 vm should have this tag"
}
}
}
output "proxmox_virtual_environment_vms_example" {
value = data.proxmox_virtual_environment_vms.example.vms
}

2
go.mod
View File

@ -4,6 +4,7 @@ go 1.18
require (
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/terraform-plugin-log v0.8.0
@ -12,6 +13,7 @@ require (
github.com/skeema/knownhosts v1.1.0
github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.7.0
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
)
require (

4
go.sum
View File

@ -27,6 +27,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -124,6 +126,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@ -322,8 +322,27 @@ func (c *VirtualEnvironmentClient) MoveVMDiskAsync(
}
// ListVMs retrieves a list of virtual machines.
func (c *VirtualEnvironmentClient) ListVMs() ([]*VirtualEnvironmentVMListResponseData, error) {
return nil, errors.New("not implemented")
func (c *VirtualEnvironmentClient) ListVMs(
ctx context.Context,
nodeName string,
) ([]*VirtualEnvironmentVMListResponseData, error) {
resBody := &VirtualEnvironmentVMListResponseBody{}
err := c.DoRequest(
ctx,
hmGET,
fmt.Sprintf("nodes/%s/qemu", url.PathEscape(nodeName)),
nil,
resBody,
)
if err != nil {
return nil, err
}
if resBody.Data == nil {
return nil, errors.New("the server did not include a data object in the response")
}
return resBody.Data, nil
}
// RebootVM reboots a virtual machine.

View File

@ -504,7 +504,9 @@ type VirtualEnvironmentVMListResponseBody struct {
// VirtualEnvironmentVMListResponseData contains the data from an virtual machine list response.
type VirtualEnvironmentVMListResponseData struct {
ACPI *CustomBool `json:"acpi,omitempty" url:"acpi,omitempty,int"`
Name *string `json:"name,omitempty"`
Tags *string `json:"tags,omitempty"`
VMID int `json:"vmid,omitempty"`
}
// VirtualEnvironmentVMMigrateRequestBody contains the body for a VM migration request.

View File

@ -0,0 +1,101 @@
/* 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 proxmoxtf
import (
"context"
"sort"
"strconv"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
mkDataSourceVirtualEnvironmentVMName = "name"
mkDataSourceVirtualEnvironmentVMNodeName = "node_name"
mkDataSourceVirtualEnvironmentVMTags = "tags"
mkDataSourceVirtualEnvironmentVMVMID = "vm_id"
)
func dataSourceVirtualEnvironmentVM() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
mkDataSourceVirtualEnvironmentVMName: {
Type: schema.TypeString,
Description: "The VM name",
Computed: true,
},
mkDataSourceVirtualEnvironmentVMNodeName: {
Type: schema.TypeString,
Description: "The node name",
Required: true,
},
mkDataSourceVirtualEnvironmentVMTags: {
Type: schema.TypeList,
Description: "Tags of the VM",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkDataSourceVirtualEnvironmentVMVMID: {
Type: schema.TypeInt,
Description: "The VM identifier",
Required: true,
},
},
ReadContext: dataSourceVirtualEnvironmentVMRead,
}
}
// dataSourceVirtualEnvironmentVMRead reads the data of a VM by ID.
func dataSourceVirtualEnvironmentVMRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
config := m.(providerConfiguration)
veClient, err := config.GetVEClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkDataSourceVirtualEnvironmentVMNodeName).(string)
vmID := d.Get(mkDataSourceVirtualEnvironmentVMVMID).(int)
vmStatus, err := veClient.GetVMStatus(ctx, nodeName, vmID)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
if vmStatus.Name != nil {
err = d.Set(mkDataSourceVirtualEnvironmentVMName, *vmStatus.Name)
} else {
err = d.Set(mkDataSourceVirtualEnvironmentVMName, "")
}
diags = append(diags, diag.FromErr(err)...)
var tags []string
if vmStatus.Tags != nil {
for _, tag := range strings.Split(*vmStatus.Tags, ";") {
t := strings.TrimSpace(tag)
if len(t) > 0 {
tags = append(tags, t)
}
}
sort.Strings(tags)
}
err = d.Set(mkDataSourceVirtualEnvironmentVMTags, tags)
diags = append(diags, diag.FromErr(err)...)
d.SetId(strconv.Itoa(vmID))
return diags
}

View File

@ -0,0 +1,41 @@
/* 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 proxmoxtf
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// TestDataSourceVirtualEnvironmentVMInstantiation tests whether the dataSourceVirtualEnvironmentVM instance can be instantiated.
func TestDataSourceVirtualEnvironmentVMInstantiation(t *testing.T) {
t.Parallel()
s := dataSourceVirtualEnvironmentVM()
if s == nil {
t.Fatalf("Cannot instantiate dataSourceVirtualEnvironmentVM")
}
}
// TestDataSourceVirtualEnvironmentVMSchema tests the dataSourceVirtualEnvironmentVM schema.
func TestDataSourceVirtualEnvironmentVMSchema(t *testing.T) {
t.Parallel()
s := dataSourceVirtualEnvironmentVM()
testComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentVMName,
mkDataSourceVirtualEnvironmentVMTags,
})
testValueTypes(t, s, map[string]schema.ValueType{
mkDataSourceVirtualEnvironmentVMName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
})
}

View File

@ -0,0 +1,154 @@
/* 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 proxmoxtf
import (
"context"
"fmt"
"sort"
"strings"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/exp/slices"
"github.com/bpg/terraform-provider-proxmox/proxmox"
)
const (
mkDataSourceVirtualEnvironmentVMs = "vms"
)
func dataSourceVirtualEnvironmentVMs() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
mkDataSourceVirtualEnvironmentVMNodeName: {
Type: schema.TypeString,
Optional: true,
Description: "The node name",
},
mkDataSourceVirtualEnvironmentVMTags: {
Type: schema.TypeList,
Description: "Tags of the VM to match",
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkDataSourceVirtualEnvironmentVMs: {
Type: schema.TypeList,
Description: "VMs",
Computed: true,
Elem: &schema.Resource{
Schema: dataSourceVirtualEnvironmentVM().Schema,
},
},
},
ReadContext: dataSourceVirtualEnvironmentVMsRead,
}
}
// dataSourceVirtualEnvironmentVMRead reads the data of a VM by ID.
func dataSourceVirtualEnvironmentVMsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
config := m.(providerConfiguration)
veClient, err := config.GetVEClient()
if err != nil {
return diag.FromErr(err)
}
nodeNames, err := getNodeNames(ctx, d, veClient)
if err != nil {
return diag.FromErr(err)
}
tagsData := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
var filterTags []string
for i := 0; i < len(tagsData); i++ {
tag := strings.TrimSpace(tagsData[i].(string))
if len(tag) > 0 {
filterTags = append(filterTags, tag)
}
}
sort.Strings(filterTags)
var vms []interface{}
for _, nodeName := range nodeNames {
listData, err := veClient.ListVMs(ctx, nodeName)
if err != nil {
diags = append(diags, diag.FromErr(err)...)
}
sort.Slice(listData, func(i, j int) bool {
return listData[i].VMID < listData[j].VMID
})
for _, data := range listData {
vm := map[string]interface{}{
mkDataSourceVirtualEnvironmentVMNodeName: nodeName,
mkDataSourceVirtualEnvironmentVMVMID: data.VMID,
}
if data.Name != nil {
vm[mkDataSourceVirtualEnvironmentVMName] = *data.Name
} else {
vm[mkDataSourceVirtualEnvironmentVMName] = ""
}
var tags []string
if data.Tags != nil && *data.Tags != "" {
tags = strings.Split(*data.Tags, ";")
sort.Strings(tags)
vm[mkDataSourceVirtualEnvironmentVMTags] = tags
}
if len(filterTags) > 0 {
match := true
for _, tag := range filterTags {
if !slices.Contains(tags, tag) {
match = false
break
}
}
if !match {
continue
}
}
vms = append(vms, vm)
}
}
err = d.Set(mkDataSourceVirtualEnvironmentVMs, vms)
diags = append(diags, diag.FromErr(err)...)
d.SetId(uuid.New().String())
return diags
}
func getNodeNames(
ctx context.Context,
d *schema.ResourceData,
veClient *proxmox.VirtualEnvironmentClient,
) ([]string, error) {
var nodeNames []string
nodeName := d.Get(mkDataSourceVirtualEnvironmentVMNodeName).(string)
if nodeName != "" {
nodeNames = append(nodeNames, nodeName)
} else {
nodes, err := veClient.ListNodes(ctx)
if err != nil {
return nil, fmt.Errorf("error listing nodes: %w", err)
}
for _, node := range nodes {
nodeNames = append(nodeNames, node.Name)
}
}
sort.Strings(nodeNames)
return nodeNames, nil
}

View File

@ -0,0 +1,53 @@
/* 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 proxmoxtf
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// TestDataSourceVirtualEnvironmentVMsInstantiation tests whether the dataSourceVirtualEnvironmentVMs instance can be instantiated.
func TestDataSourceVirtualEnvironmentVMsInstantiation(t *testing.T) {
t.Parallel()
s := dataSourceVirtualEnvironmentVMs()
if s == nil {
t.Fatalf("Cannot instantiate dataSourceVirtualEnvironmentVMs")
}
}
// TestDataSourceVirtualEnvironmentVMsSchema tests the dataSourceVirtualEnvironmentVMs schema.
func TestDataSourceVirtualEnvironmentVMsSchema(t *testing.T) {
t.Parallel()
s := dataSourceVirtualEnvironmentVMs()
testComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentVMs,
})
testValueTypes(t, s, map[string]schema.ValueType{
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
mkDataSourceVirtualEnvironmentVMs: schema.TypeList,
})
vmsSchema := testNestedSchemaExistence(t, s, mkDataSourceVirtualEnvironmentVMs)
testComputedAttributes(t, vmsSchema, []string{
mkDataSourceVirtualEnvironmentVMName,
mkDataSourceVirtualEnvironmentVMTags,
})
testValueTypes(t, vmsSchema, map[string]schema.ValueType{
mkDataSourceVirtualEnvironmentVMName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMNodeName: schema.TypeString,
mkDataSourceVirtualEnvironmentVMTags: schema.TypeList,
mkDataSourceVirtualEnvironmentVMVMID: schema.TypeInt,
})
}

View File

@ -55,6 +55,8 @@ func Provider() *schema.Provider {
"proxmox_virtual_environment_user": dataSourceVirtualEnvironmentUser(),
"proxmox_virtual_environment_users": dataSourceVirtualEnvironmentUsers(),
"proxmox_virtual_environment_version": dataSourceVirtualEnvironmentVersion(),
"proxmox_virtual_environment_vm": dataSourceVirtualEnvironmentVM(),
"proxmox_virtual_environment_vms": dataSourceVirtualEnvironmentVMs(),
},
ResourcesMap: map[string]*schema.Resource{
"proxmox_virtual_environment_certificate": resourceVirtualEnvironmentCertificate(),