0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-22 19:38:35 +00:00

chore(vm2): cleanup cpu implementation, refactor rearrange acc tests (#1311)

* chore(vm2): cleanup `cpu` implementation, refactor rearrange acc tests

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-05-18 23:31:50 -04:00 committed by GitHub
parent aa309fd9ea
commit 2e34c57f6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 897 additions and 919 deletions

View File

@ -26,6 +26,7 @@ jobs:
node: pve3
port: 13453
runs-on: ${{ matrix.os }}
timeout-minutes: 30
environment: pve-acc
steps:
- name: Dump GitHub context

View File

@ -23,7 +23,7 @@ issues:
- path: _types\.go
linters:
- lll
- path: fwprovider/tests/.*\.go
- path: fwprovider/.*_test\.go
linters:
- paralleltest
# Exclude `lll` issues for long lines with URLs.

View File

@ -95,7 +95,7 @@ test:
.PHONY: testacc
testacc:
@# explicitly add TF_ACC=1 to trigger the acceptance tests, `testacc.env` might be missing or incomplete
@TF_ACC=1 env $$(cat testacc.env | xargs) go test --timeout=30m -count=1 -v github.com/bpg/terraform-provider-proxmox/fwprovider/tests/...
@TF_ACC=1 env $$(cat testacc.env | xargs) go test --timeout=30m -count=1 -v github.com/bpg/terraform-provider-proxmox/fwprovider/...
.PHONY: lint
lint:

View File

@ -78,24 +78,24 @@ resource "proxmox_virtual_environment_file" "cloud_config" {
node_name = "pve"
source_raw {
data = <<EOF
#cloud-config
users:
- default
- name: ubuntu
groups:
- sudo
shell: /bin/bash
ssh_authorized_keys:
- ${trimspace(data.local_file.ssh_public_key.content)}
sudo: ALL=(ALL) NOPASSWD:ALL
runcmd:
- apt update
- apt install -y qemu-guest-agent net-tools
- timedatectl set-timezone America/Toronto
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "done" > /tmp/cloud-config.done
data = <<-EOF
#cloud-config
users:
- default
- name: ubuntu
groups:
- sudo
shell: /bin/bash
ssh_authorized_keys:
- ${trimspace(data.local_file.ssh_public_key.content)}
sudo: ALL=(ALL) NOPASSWD:ALL
runcmd:
- apt update
- apt install -y qemu-guest-agent net-tools
- timedatectl set-timezone America/Toronto
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "done" > /tmp/cloud-config.done
EOF
file_name = "cloud-config.yaml"

View File

@ -56,24 +56,24 @@ resource "proxmox_virtual_environment_file" "cloud_config" {
node_name = "pve"
source_raw {
data = <<EOF
#cloud-config
chpasswd:
list: |
ubuntu:example
expire: false
hostname: example-hostname
packages:
- qemu-guest-agent
users:
- default
- name: ubuntu
groups: sudo
shell: /bin/bash
ssh-authorized-keys:
- ${trimspace(tls_private_key.example.public_key_openssh)}
sudo: ALL=(ALL) NOPASSWD:ALL
EOF
data = <<-EOF
#cloud-config
chpasswd:
list: |
ubuntu:example
expire: false
hostname: example-hostname
packages:
- qemu-guest-agent
users:
- default
- name: ubuntu
groups: sudo
shell: /bin/bash
ssh-authorized-keys:
- ${trimspace(tls_private_key.example.public_key_openssh)}
sudo: ALL=(ALL) NOPASSWD:ALL
EOF
file_name = "example.cloud-config.yaml"
}

View File

@ -14,12 +14,10 @@ This is an experimental implementation of a Proxmox VM resource using Plugin Fra
-> Many attributes are marked as **optional** _and_ **computed** in the schema,
hence you may seem added to the plan with "(known after apply)" status, even if they are not set in the configuration.
This is done to support the `clone` operation, when a VM is created from an existing one,
and attributes of the original VM are copied to the new one.<br><br>
This is done to support the `clone` operation, when a VM is created from an existing VM or template,
and the source attributes are copied to the clone.<br><br>
Computed attributes allow the provider to set those attributes without user input.
The attributes are marked as optional to allow the user to set (or overwrite) them if needed.
In order to remove the computed attribute from the plan, you can set it to an empty value (e.g. `""` for string, `[]` for collection).
The attributes are also marked as optional to allow the practitioner to set (or overwrite) them if needed.

View File

@ -8,21 +8,21 @@ resource "proxmox_virtual_environment_file" "user_config" {
node_name = data.proxmox_virtual_environment_datastores.example.node_name
source_raw {
data = <<EOF
#cloud-config
chpasswd:
list: |
ubuntu:example
expire: false
hostname: terraform-provider-proxmox-example
users:
- default
- name: ubuntu
groups: sudo
shell: /bin/bash
ssh-authorized-keys:
- ${trimspace(tls_private_key.example.public_key_openssh)}
sudo: ALL=(ALL) NOPASSWD:ALL
data = <<-EOF
#cloud-config
chpasswd:
list: |
ubuntu:example
expire: false
hostname: terraform-provider-proxmox-example
users:
- default
- name: ubuntu
groups: sudo
shell: /bin/bash
ssh-authorized-keys:
- ${trimspace(tls_private_key.example.public_key_openssh)}
sudo: ALL=(ALL) NOPASSWD:ALL
EOF
file_name = "terraform-provider-proxmox-example-user-config.yaml"

View File

@ -8,24 +8,24 @@ resource "proxmox_virtual_environment_file" "cloud_config" {
node_name = "pve"
source_raw {
data = <<EOF
#cloud-config
users:
- default
- name: ubuntu
groups:
- sudo
shell: /bin/bash
ssh_authorized_keys:
- ${trimspace(data.local_file.ssh_public_key.content)}
sudo: ALL=(ALL) NOPASSWD:ALL
runcmd:
- apt update
- apt install -y qemu-guest-agent net-tools
- timedatectl set-timezone America/Toronto
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "done" > /tmp/cloud-config.done
data = <<-EOF
#cloud-config
users:
- default
- name: ubuntu
groups:
- sudo
shell: /bin/bash
ssh_authorized_keys:
- ${trimspace(data.local_file.ssh_public_key.content)}
sudo: ALL=(ALL) NOPASSWD:ALL
runcmd:
- apt update
- apt install -y qemu-guest-agent net-tools
- timedatectl set-timezone America/Toronto
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
- echo "done" > /tmp/cloud-config.done
EOF
file_name = "cloud-config.yaml"

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package access_test
import (
"context"
@ -17,47 +17,50 @@ import (
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
)
func TestAccAcl_User(t *testing.T) {
te := initTestEnvironment(t)
t.Parallel()
te := test.InitEnvironment(t)
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
te.addTemplateVars(map[string]any{
te.AddTemplateVars(map[string]any{
"UserID": userID,
})
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
CheckDestroy: nil,
PreCheck: func() {
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
err := te.AccessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
ID: userID,
Password: gofakeit.Password(true, true, true, true, false, 8),
})
require.NoError(t, err)
t.Cleanup(func() {
err := te.accessClient().DeleteUser(context.Background(), userID)
err := te.AccessClient().DeleteUser(context.Background(), userID)
require.NoError(t, err)
})
},
Steps: []resource.TestStep{
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_acl" "test" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_acl" "test" {
user_id = "{{.UserID}}"
path = "/"
role_id = "NoAccess"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
"path": "/",
"role_id": "NoAccess",
"user_id": userID,
"propagate": "true",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
"group_id",
"token_id",
}),
@ -70,19 +73,19 @@ func TestAccAcl_User(t *testing.T) {
ImportStateVerify: true,
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_acl" "test" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_acl" "test" {
user_id = "{{.UserID}}"
path = "/"
role_id = "PVEPoolUser"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
"path": "/",
"role_id": "PVEPoolUser",
"user_id": userID,
"propagate": "true",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
"group_id",
"token_id",
}),
@ -95,10 +98,10 @@ func TestAccAcl_User(t *testing.T) {
func TestAccAcl_Validators(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
resource.UnitTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
CheckDestroy: nil,
Steps: []resource.TestStep{
{

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package access_test
import (
"context"
@ -15,16 +15,17 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
)
func TestAccResourceUser(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
te.addTemplateVars(map[string]any{
te.AddTemplateVars(map[string]any{
"UserID": userID,
})
@ -34,7 +35,7 @@ func TestAccResourceUser(t *testing.T) {
}{
{"create and update user", []resource.TestStep{
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_user" "user" {
comment = "Managed by Terraform"
email = "{{.UserID}}"
enabled = true
@ -43,7 +44,7 @@ func TestAccResourceUser(t *testing.T) {
last_name = "Last"
user_id = "{{.UserID}}"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
Check: test.ResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
"comment": "Managed by Terraform",
"email": userID,
"enabled": "true",
@ -54,13 +55,13 @@ func TestAccResourceUser(t *testing.T) {
}),
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_user" "user" {
enabled = false
expiration_date = "2035-01-01T22:00:00Z"
user_id = "{{.UserID}}"
first_name = "First One"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
Check: test.ResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
"enabled": "false",
"expiration_date": "2035-01-01T22:00:00Z",
"first_name": "First One",
@ -78,7 +79,7 @@ func TestAccResourceUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})
@ -88,11 +89,11 @@ func TestAccResourceUser(t *testing.T) {
func TestAccResourceUserToken(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
tokenName := gofakeit.Word()
te.addTemplateVars(map[string]any{
te.AddTemplateVars(map[string]any{
"UserID": userID,
"TokenName": tokenName,
})
@ -105,25 +106,25 @@ func TestAccResourceUserToken(t *testing.T) {
{
"create and update user token",
func() {
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
err := te.AccessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
ID: userID,
Password: gofakeit.Password(true, true, true, true, false, 8),
})
require.NoError(t, err)
t.Cleanup(func() {
err := te.accessClient().DeleteUser(context.Background(), userID)
err := te.AccessClient().DeleteUser(context.Background(), userID)
require.NoError(t, err)
})
},
[]resource.TestStep{
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
comment = "Managed by Terraform"
token_name = "{{.TokenName}}"
user_id = "{{.UserID}}"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
Check: test.ResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
"comment": "Managed by Terraform",
"id": fmt.Sprintf("%s!%s", userID, tokenName),
"user_id": userID,
@ -131,7 +132,7 @@ func TestAccResourceUserToken(t *testing.T) {
}),
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
comment = "Managed by Terraform 2"
expiration_date = "2033-01-01T01:01:01Z"
privileges_separation = false
@ -139,33 +140,33 @@ func TestAccResourceUserToken(t *testing.T) {
user_id = "{{.UserID}}"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
"comment": "Managed by Terraform 2",
"expiration_date": "2033-01-01T01:01:01Z",
"privileges_separation": "false",
"token_name": tokenName,
"user_id": userID,
}),
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
"value",
}),
),
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
comment = "Managed by Terraform 2"
privileges_separation = false
token_name = "{{.TokenName}}"
user_id = "{{.UserID}}"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
"comment": "Managed by Terraform 2",
"privileges_separation": "false",
"token_name": tokenName,
"user_id": userID,
}),
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
"expiration_date",
"value",
}),
@ -183,7 +184,7 @@ func TestAccResourceUserToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
PreCheck: tt.preCheck,
Steps: tt.steps,
})

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package hardwaremapping_test
import (
"fmt"
@ -18,7 +18,7 @@ import (
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
hwm "github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
@ -41,7 +41,7 @@ type accTestHardwareMappingFakeData struct {
Names []string `fake:"{noun}" fakesize:"2"`
}
func testAccResourceHardwareMappingInit(t *testing.T) (*accTestHardwareMappingFakeData, *testEnvironment) {
func testAccResourceHardwareMappingInit(t *testing.T) (*accTestHardwareMappingFakeData, *test.Environment) {
t.Helper()
// Register a new custom function to generate random Linux device IDs.
@ -81,7 +81,7 @@ func testAccResourceHardwareMappingInit(t *testing.T) (*accTestHardwareMappingFa
},
)
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
var data accTestHardwareMappingFakeData
@ -102,7 +102,7 @@ func TestAccResourceHardwareMappingPCIValidInput(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Test the "Create" and "Read" implementations where all possible attributes are specified.
{
@ -129,7 +129,7 @@ func TestAccResourceHardwareMappingPCIValidInput(t *testing.T) {
data.MapComments[0],
data.MapDeviceIDs[0],
data.MapIOMMUGroups[0],
te.nodeName,
te.NodeName,
data.MapPathsPCI[0],
data.MapSubsystemIDs[0],
data.MediatedDevices,
@ -142,7 +142,7 @@ func TestAccResourceHardwareMappingPCIValidInput(t *testing.T) {
"comment": data.MapComments[0],
"id": data.MapDeviceIDs[0],
"iommu_group": strconv.Itoa(int(data.MapIOMMUGroups[0])),
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsPCI[0],
"subsystem_id": data.MapSubsystemIDs[0],
},
@ -189,7 +189,7 @@ func TestAccResourceHardwareMappingPCIValidInput(t *testing.T) {
data.MapComments[1],
data.MapDeviceIDs[0],
data.MapIOMMUGroups[1],
te.nodeName,
te.NodeName,
data.MapPathsPCI[1],
data.MapSubsystemIDs[1],
!data.MediatedDevices,
@ -202,7 +202,7 @@ func TestAccResourceHardwareMappingPCIValidInput(t *testing.T) {
"comment": data.MapComments[1],
"id": data.MapDeviceIDs[0],
"iommu_group": strconv.Itoa(int(data.MapIOMMUGroups[1])),
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsPCI[1],
"subsystem_id": data.MapSubsystemIDs[1],
},
@ -232,7 +232,7 @@ func TestAccResourceHardwareMappingPCIValidInputMinimal(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Test the "Create" and "Read" implementations with only the minimum amount of attributes being set.
{
@ -251,7 +251,7 @@ func TestAccResourceHardwareMappingPCIValidInputMinimal(t *testing.T) {
`,
data.Names[0],
data.MapDeviceIDs[0],
te.nodeName,
te.NodeName,
data.MapPathsPCI[0],
),
ConfigStateChecks: []statecheck.StateCheck{
@ -273,7 +273,7 @@ func TestAccResourceHardwareMappingPCIValidInputMinimal(t *testing.T) {
resource.TestCheckTypeSetElemNestedAttrs(
accTestHardwareMappingNamePCI, "map.*", map[string]string{
"id": data.MapDeviceIDs[0],
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsPCI[0],
},
),
@ -314,7 +314,7 @@ func TestAccResourceHardwareMappingPCIValidInputMinimal(t *testing.T) {
data.MapComments[1],
data.MapDeviceIDs[0],
data.MapIOMMUGroups[1],
te.nodeName,
te.NodeName,
data.MapPathsPCI[1],
data.MapSubsystemIDs[1],
!data.MediatedDevices,
@ -327,7 +327,7 @@ func TestAccResourceHardwareMappingPCIValidInputMinimal(t *testing.T) {
"comment": data.MapComments[1],
"id": data.MapDeviceIDs[0],
"iommu_group": strconv.Itoa(int(data.MapIOMMUGroups[1])),
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsPCI[1],
"subsystem_id": data.MapSubsystemIDs[1],
},
@ -355,7 +355,7 @@ func TestAccResourceHardwareMappingPCIInvalidInput(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
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.
@ -370,7 +370,7 @@ func TestAccResourceHardwareMappingPCIInvalidInput(t *testing.T) {
// References:
// 1. https://pkg.go.dev/regexp/syntax
`(?s).*%s(?s).*`,
hwm.ErrResourceMessageInvalidPath(proxmoxtypes.TypePCI),
`not a valid Linux device path for hardware mapping of type "`+proxmoxtypes.TypePCI.String()+`"`,
),
),
Config: fmt.Sprintf(
@ -397,7 +397,7 @@ func TestAccResourceHardwareMappingPCIInvalidInput(t *testing.T) {
data.Comments[1],
data.MapDeviceIDs[0],
data.MapIOMMUGroups[0],
te.nodeName,
te.NodeName,
data.MapSubsystemIDs[0],
data.MediatedDevices,
),
@ -408,7 +408,7 @@ func TestAccResourceHardwareMappingPCIInvalidInput(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
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 subsystem
@ -439,7 +439,7 @@ func TestAccResourceHardwareMappingPCIInvalidInput(t *testing.T) {
data.Comments[1],
data.MapDeviceIDs[0],
data.MapIOMMUGroups[0],
te.nodeName,
te.NodeName,
data.MapPathsPCI[0],
data.MediatedDevices,
),
@ -459,7 +459,7 @@ func TestAccResourceHardwareMappingUSBValidInput(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Test the "Create" and "Read" implementations where all possible attributes are specified.
{
@ -482,7 +482,7 @@ func TestAccResourceHardwareMappingUSBValidInput(t *testing.T) {
data.Names[0],
data.MapComments[0],
data.MapDeviceIDs[0],
te.nodeName,
te.NodeName,
data.MapPathsUSB[0],
),
Check: resource.ComposeTestCheckFunc(
@ -492,7 +492,7 @@ func TestAccResourceHardwareMappingUSBValidInput(t *testing.T) {
accTestHardwareMappingNameUSB, "map.*", map[string]string{
"comment": data.MapComments[0],
"id": data.MapDeviceIDs[0],
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsUSB[0],
},
),
@ -529,7 +529,7 @@ func TestAccResourceHardwareMappingUSBValidInput(t *testing.T) {
data.Names[0],
data.MapComments[1],
data.MapDeviceIDs[0],
te.nodeName,
te.NodeName,
data.MapPathsUSB[1],
),
Check: resource.ComposeTestCheckFunc(
@ -539,7 +539,7 @@ func TestAccResourceHardwareMappingUSBValidInput(t *testing.T) {
accTestHardwareMappingNameUSB, "map.*", map[string]string{
"comment": data.MapComments[1],
"id": data.MapDeviceIDs[0],
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsUSB[1],
},
),
@ -563,7 +563,7 @@ func TestAccResourceHardwareMappingUSBValidInputMinimal(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Test the "Create" and "Read" implementations with only the minimum amount of attributes being set.
{
@ -581,7 +581,7 @@ func TestAccResourceHardwareMappingUSBValidInputMinimal(t *testing.T) {
`,
data.Names[0],
data.MapDeviceIDs[0],
te.nodeName,
te.NodeName,
),
ConfigStateChecks: []statecheck.StateCheck{
// Optional attributes should all be unset.
@ -600,7 +600,7 @@ func TestAccResourceHardwareMappingUSBValidInputMinimal(t *testing.T) {
resource.TestCheckTypeSetElemNestedAttrs(
accTestHardwareMappingNameUSB, "map.*", map[string]string{
"id": data.MapDeviceIDs[0],
"node": te.nodeName,
"node": te.NodeName,
},
),
resource.TestCheckResourceAttr(accTestHardwareMappingNameUSB, "name", data.Names[0]),
@ -628,7 +628,7 @@ func TestAccResourceHardwareMappingUSBValidInputMinimal(t *testing.T) {
data.Names[0],
data.Comments[1],
data.MapDeviceIDs[1],
te.nodeName,
te.NodeName,
data.MapPathsUSB[0],
),
Check: resource.ComposeTestCheckFunc(
@ -638,7 +638,7 @@ func TestAccResourceHardwareMappingUSBValidInputMinimal(t *testing.T) {
accTestHardwareMappingNameUSB, "map.*", map[string]string{
"comment": data.Comments[1],
"id": data.MapDeviceIDs[1],
"node": te.nodeName,
"node": te.NodeName,
"path": data.MapPathsUSB[0],
},
),
@ -659,7 +659,7 @@ func TestAccResourceHardwareMappingUSBInvalidInput(t *testing.T) {
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
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.
@ -684,7 +684,7 @@ func TestAccResourceHardwareMappingUSBInvalidInput(t *testing.T) {
data.Names[0],
data.Comments[1],
data.MapDeviceIDs[0],
te.nodeName,
te.NodeName,
),
ExpectError: regexp.MustCompile(`valid Linux device path for hardware mapping of type "usb"`),
},

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package network_test
import (
"fmt"
@ -13,10 +13,12 @@ import (
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
)
func TestAccResourceLinuxBridge(t *testing.T) {
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
iface := fmt.Sprintf("vmbr%d", gofakeit.Number(10, 9999))
ipV4cidr1 := fmt.Sprintf("%s/24", gofakeit.IPv4Address())
@ -24,11 +26,11 @@ func TestAccResourceLinuxBridge(t *testing.T) {
ipV6cidr := "FE80:0000:0000:0000:0202:B3FF:FE1E:8329/64"
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: te.renderConfig(fmt.Sprintf(`
Config: te.RenderConfig(fmt.Sprintf(`
resource "proxmox_virtual_environment_network_linux_bridge" "test" {
address = "%s"
autostart = true
@ -40,7 +42,7 @@ func TestAccResourceLinuxBridge(t *testing.T) {
}
`, ipV4cidr1, iface)),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_network_linux_bridge.test", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_network_linux_bridge.test", map[string]string{
"address": ipV4cidr1,
"autostart": "true",
"comment": "created by terraform",
@ -48,14 +50,14 @@ func TestAccResourceLinuxBridge(t *testing.T) {
"name": iface,
"vlan_aware": "true",
}),
testResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
test.ResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
"id",
}),
),
},
// Update testing
{
Config: te.renderConfig(fmt.Sprintf(`
Config: te.RenderConfig(fmt.Sprintf(`
resource "proxmox_virtual_environment_network_linux_bridge" "test" {
address = "%s"
address6 = "%s"
@ -67,7 +69,7 @@ func TestAccResourceLinuxBridge(t *testing.T) {
vlan_aware = false
}`, ipV4cidr2, ipV6cidr, iface)),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_network_linux_bridge.test", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_network_linux_bridge.test", map[string]string{
"address": ipV4cidr2,
"address6": ipV6cidr,
"autostart": "false",
@ -75,10 +77,10 @@ func TestAccResourceLinuxBridge(t *testing.T) {
"name": iface,
"vlan_aware": "false",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
"mtu",
}),
testResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
test.ResourceAttributesSet("proxmox_virtual_environment_network_linux_bridge.test", []string{
"id",
}),
),

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package network_test
import (
"fmt"
@ -13,6 +13,8 @@ import (
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
)
const (
@ -20,7 +22,7 @@ const (
)
func TestAccResourceLinuxVLAN(t *testing.T) {
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
iface := "ens18"
vlan1 := gofakeit.Number(10, 4094)
@ -29,11 +31,11 @@ func TestAccResourceLinuxVLAN(t *testing.T) {
ipV4cidr := fmt.Sprintf("%s/24", gofakeit.IPv4Address())
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: te.renderConfig(testAccResourceLinuxVLANCreatedConfig(iface, vlan1)),
Config: te.RenderConfig(testAccResourceLinuxVLANCreatedConfig(iface, vlan1)),
Check: testAccResourceLinuxVLANCreatedCheck(iface, vlan1),
},
// ImportState testing
@ -44,7 +46,7 @@ func TestAccResourceLinuxVLAN(t *testing.T) {
},
// Create and Read with a custom name
{
Config: te.renderConfig(testAccResourceLinuxVLANCustomNameCreatedConfig(customName, iface, vlan2)),
Config: te.RenderConfig(testAccResourceLinuxVLANCustomNameCreatedConfig(customName, iface, vlan2)),
Check: testAccResourceLinuxVLANCustomNameCreatedCheck(customName, iface, vlan2),
// PVE API is unreliable. Sometimes it returns a wrong VLAN ID for this second interface.
SkipFunc: func() (bool, error) {
@ -53,7 +55,7 @@ func TestAccResourceLinuxVLAN(t *testing.T) {
},
// Update testing
{
Config: te.renderConfig(testAccResourceLinuxVLANUpdatedConfig(iface, vlan1, ipV4cidr)),
Config: te.RenderConfig(testAccResourceLinuxVLANUpdatedConfig(iface, vlan1, ipV4cidr)),
Check: testAccResourceLinuxVLANUpdatedCheck(iface, vlan1, ipV4cidr),
},
},

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package fwprovider_test
import (
"context"
@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/storage"
)
@ -24,9 +25,9 @@ const (
)
func TestAccResourceDownloadFile(t *testing.T) {
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
te.addTemplateVars(map[string]interface{}{
te.AddTemplateVars(map[string]interface{}{
"FakeFileISO": fakeFileISO,
"FakeFileQCOW2": fakeFileQCOW2,
})
@ -36,7 +37,7 @@ func TestAccResourceDownloadFile(t *testing.T) {
steps []resource.TestStep
}{
{"download qcow2 file", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "qcow2_image" {
content_type = "iso"
node_name = "{{.NodeName}}"
@ -48,11 +49,11 @@ func TestAccResourceDownloadFile(t *testing.T) {
overwrite_unmanaged = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_download_file.qcow2_image", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_download_file.qcow2_image", map[string]string{
"id": "local:iso/fake_qcow2_file.img",
"content_type": "iso",
"node_name": te.nodeName,
"datastore_id": te.datastoreID,
"node_name": te.NodeName,
"datastore_id": te.DatastoreID,
"url": fakeFileQCOW2,
"file_name": "fake_qcow2_file.img",
"upload_timeout": "600",
@ -61,14 +62,14 @@ func TestAccResourceDownloadFile(t *testing.T) {
"checksum": "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6",
"checksum_algorithm": "sha256",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_download_file.qcow2_image", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_download_file.qcow2_image", []string{
"decompression_algorithm",
}),
),
}}},
{"download & update iso file", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "iso_image" {
content_type = "iso"
node_name = "{{.NodeName}}"
@ -77,17 +78,17 @@ func TestAccResourceDownloadFile(t *testing.T) {
overwrite_unmanaged = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_download_file.iso_image", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_download_file.iso_image", map[string]string{
"id": "local:iso/fake_file.iso",
"node_name": te.nodeName,
"datastore_id": te.datastoreID,
"node_name": te.NodeName,
"datastore_id": te.DatastoreID,
"url": fakeFileISO,
"file_name": "fake_file.iso",
"upload_timeout": "600",
"size": "3",
"verify": "true",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image", []string{
"checksum",
"checksum_algorithm",
"decompression_algorithm",
@ -95,7 +96,7 @@ func TestAccResourceDownloadFile(t *testing.T) {
),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "iso_image" {
content_type = "iso"
node_name = "{{.NodeName}}"
@ -106,18 +107,18 @@ func TestAccResourceDownloadFile(t *testing.T) {
overwrite_unmanaged = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_download_file.iso_image", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_download_file.iso_image", map[string]string{
"id": "local:iso/fake_iso_file.img",
"content_type": "iso",
"node_name": te.nodeName,
"datastore_id": te.datastoreID,
"node_name": te.NodeName,
"datastore_id": te.DatastoreID,
"url": fakeFileISO,
"file_name": "fake_iso_file.img",
"upload_timeout": "10000",
"size": "3",
"verify": "true",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image", []string{
"checksum",
"checksum_algorithm",
"decompression_algorithm",
@ -130,23 +131,23 @@ func TestAccResourceDownloadFile(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
_ = te.nodeStorageClient().DeleteDatastoreFile(ctx, "iso/fake_file.iso") //nolint: errcheck
_ = te.NodeStorageClient().DeleteDatastoreFile(ctx, "iso/fake_file.iso") //nolint: errcheck
err := te.nodeStorageClient().DownloadFileByURL(ctx, &storage.DownloadURLPostRequestBody{
err := te.NodeStorageClient().DownloadFileByURL(ctx, &storage.DownloadURLPostRequestBody{
Content: ptr.Ptr("iso"),
FileName: ptr.Ptr("fake_file.iso"),
Node: ptr.Ptr(te.nodeName),
Storage: ptr.Ptr(te.datastoreID),
Node: ptr.Ptr(te.NodeName),
Storage: ptr.Ptr(te.DatastoreID),
URL: ptr.Ptr(fakeFileISO),
})
require.NoError(t, err)
t.Cleanup(func() {
e := te.nodeStorageClient().DeleteDatastoreFile(context.Background(), "iso/fake_file.iso")
e := te.NodeStorageClient().DeleteDatastoreFile(context.Background(), "iso/fake_file.iso")
require.NoError(t, e)
})
},
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "iso_image3" {
content_type = "iso"
node_name = "{{.NodeName}}"
@ -156,17 +157,17 @@ func TestAccResourceDownloadFile(t *testing.T) {
overwrite_unmanaged = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_download_file.iso_image3", map[string]string{
test.ResourceAttributes("proxmox_virtual_environment_download_file.iso_image3", map[string]string{
"id": "local:iso/fake_iso_file3.iso",
"content_type": "iso",
"node_name": te.nodeName,
"datastore_id": te.datastoreID,
"node_name": te.NodeName,
"datastore_id": te.DatastoreID,
"url": fakeFileISO,
"file_name": "fake_iso_file3.iso",
"size": "3",
"verify": "true",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image3", []string{
test.NoResourceAttributesSet("proxmox_virtual_environment_download_file.iso_image3", []string{
"checksum",
"checksum_algorithm",
"decompression_algorithm",
@ -178,7 +179,7 @@ func TestAccResourceDownloadFile(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package fwprovider_test
import (
"fmt"
@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/bpg/terraform-provider-proxmox/fwprovider"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
)
const accTestClusterOptionsName = "proxmox_virtual_environment_cluster_options.test_options"
@ -20,11 +20,11 @@ const accTestClusterOptionsName = "proxmox_virtual_environment_cluster_options.t
func TestAccResourceClusterOptions(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := test.InitEnvironment(t)
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Create and Read testing
{
@ -77,8 +77,8 @@ func testAccResourceClusterOptionsCreatedConfig() string {
}
}
`,
fwprovider.ClusterOptionsNextIDLowerMinimum,
fwprovider.ClusterOptionsNextIDLowerMaximum,
100,
999999999,
)
}
@ -100,12 +100,12 @@ func testAccResourceClusterOptionsCreatedCheck() resource.TestCheckFunc {
resource.TestCheckResourceAttr(
accTestClusterOptionsName,
"next_id.lower",
fmt.Sprintf("%d", fwprovider.ClusterOptionsNextIDLowerMinimum),
fmt.Sprintf("%d", 100),
),
resource.TestCheckResourceAttr(
accTestClusterOptionsName,
"next_id.upper",
fmt.Sprintf("%d", fwprovider.ClusterOptionsNextIDLowerMaximum),
fmt.Sprintf("%d", 999999999),
),
resource.TestCheckResourceAttr(accTestClusterOptionsName, "notify.ha_fencing_mode", "never"),
resource.TestCheckResourceAttr(accTestClusterOptionsName, "notify.ha_fencing_target", "default-matcher"),

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"testing"
@ -15,16 +15,16 @@ import (
func TestAccDatasourceNode(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
steps []resource.TestStep
}{
{"read node attributes", []resource.TestStep{{
Config: te.renderConfig(`data "proxmox_virtual_environment_node" "test" { node_name = "{{.NodeName}}" }`),
Config: te.RenderConfig(`data "proxmox_virtual_environment_node" "test" { node_name = "{{.NodeName}}" }`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributesSet("data.proxmox_virtual_environment_node.test", []string{
ResourceAttributesSet("data.proxmox_virtual_environment_node.test", []string{
"cpu_count",
"cpu_sockets",
"cpu_model",
@ -40,7 +40,7 @@ func TestAccDatasourceNode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"fmt"
@ -15,12 +15,12 @@ import (
)
func TestAccDatasourceVersion(t *testing.T) {
te := initTestEnvironment(t)
te := InitEnvironment(t)
datasourceName := "data.proxmox_virtual_environment_version.test"
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
// Read testing
{

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"context"
@ -32,24 +32,24 @@ func TestAccResourceContainer(t *testing.T) { //nolint:wsl
// download fails with 404 or "exit code 8" if run in parallel
// t.Parallel()
te := initTestEnvironment(t)
te := InitEnvironment(t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: []resource.TestStep{
{
Config: te.renderConfig(testAccResourceContainerCreateConfig(te, false)),
Config: te.RenderConfig(testAccResourceContainerCreateConfig(te, false)),
Check: testAccResourceContainerCreateCheck(te),
},
{
Config: te.renderConfig(testAccResourceContainerCreateConfig(te, true) + testAccResourceContainerCreateCloneConfig(te)),
Config: te.RenderConfig(testAccResourceContainerCreateConfig(te, true) + testAccResourceContainerCreateCloneConfig(te)),
Check: testAccResourceContainerCreateCloneCheck(te),
},
},
})
}
func testAccResourceContainerCreateConfig(te *testEnvironment, isTemplate bool) string {
func testAccResourceContainerCreateConfig(te *Environment, isTemplate bool) string {
te.t.Helper()
return fmt.Sprintf(`
@ -98,7 +98,7 @@ resource "proxmox_virtual_environment_container" "test_container" {
`, accTestContainerID, isTemplate)
}
func testAccResourceContainerCreateCheck(te *testEnvironment) resource.TestCheckFunc {
func testAccResourceContainerCreateCheck(te *Environment) resource.TestCheckFunc {
te.t.Helper()
return resource.ComposeTestCheckFunc(
@ -107,7 +107,7 @@ func testAccResourceContainerCreateCheck(te *testEnvironment) resource.TestCheck
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := te.nodeClient().Container(accTestContainerID).WaitForContainerStatus(ctx, "running")
err := te.NodeClient().Container(accTestContainerID).WaitForContainerStatus(ctx, "running")
require.NoError(te.t, err, "container did not start")
return nil
@ -115,7 +115,7 @@ func testAccResourceContainerCreateCheck(te *testEnvironment) resource.TestCheck
)
}
func testAccResourceContainerCreateCloneConfig(te *testEnvironment) string {
func testAccResourceContainerCreateCloneConfig(te *Environment) string {
te.t.Helper()
return fmt.Sprintf(`
@ -135,7 +135,7 @@ resource "proxmox_virtual_environment_container" "test_container_clone" {
`, accCloneContainerID)
}
func testAccResourceContainerCreateCloneCheck(te *testEnvironment) resource.TestCheckFunc {
func testAccResourceContainerCreateCloneCheck(te *Environment) resource.TestCheckFunc {
te.t.Helper()
return resource.ComposeTestCheckFunc(
@ -143,7 +143,7 @@ func testAccResourceContainerCreateCloneCheck(te *testEnvironment) resource.Test
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := te.nodeClient().Container(accCloneContainerID).WaitForContainerStatus(ctx, "running")
err := te.NodeClient().Container(accCloneContainerID).WaitForContainerStatus(ctx, "running")
require.NoError(te.t, err, "container did not start")
return nil

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"context"
@ -36,7 +36,7 @@ func (c *nodeResolver) Resolve(_ context.Context, _ string) (ssh.ProxmoxNode, er
}
func TestAccResourceFile(t *testing.T) {
te := initTestEnvironment(t)
te := InitEnvironment(t)
snippetRaw := fmt.Sprintf("snippet-raw-%s.txt", gofakeit.Word())
snippetURL := "https://raw.githubusercontent.com/yaml/yaml-test-suite/main/src/229Q.yaml"
@ -44,7 +44,7 @@ func TestAccResourceFile(t *testing.T) {
snippetFile2 := strings.ReplaceAll(createFile(t, "snippet-file-2-*.yaml", "test snippet 2 - file").Name(), `\`, `/`)
fileISO := strings.ReplaceAll(createFile(t, "file-*.iso", "pretend it is an ISO").Name(), `\`, `/`)
te.addTemplateVars(map[string]interface{}{
te.AddTemplateVars(map[string]interface{}{
"SnippetRaw": snippetRaw,
"SnippetURL": snippetURL,
"SnippetFile1": snippetFile1,
@ -53,7 +53,7 @@ func TestAccResourceFile(t *testing.T) {
})
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
PreCheck: func() {
uploadSnippetFile(t, snippetFile2)
t.Cleanup(func() {
@ -67,7 +67,7 @@ func TestAccResourceFile(t *testing.T) {
},
Steps: []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test_raw" {
content_type = "snippets"
datastore_id = "local"
@ -79,7 +79,7 @@ func TestAccResourceFile(t *testing.T) {
file_name = "{{.SnippetRaw}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test_raw", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test_raw", map[string]string{
"content_type": "snippets",
"file_name": snippetRaw,
"source_raw.0.file_name": snippetRaw,
@ -88,7 +88,7 @@ func TestAccResourceFile(t *testing.T) {
}),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -96,14 +96,14 @@ func TestAccResourceFile(t *testing.T) {
path = "{{.SnippetFile1}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
"content_type": "snippets",
"file_name": filepath.Base(snippetFile1),
"id": fmt.Sprintf("local:snippets/%s", filepath.Base(snippetFile1)),
}),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -111,14 +111,14 @@ func TestAccResourceFile(t *testing.T) {
path = "{{.SnippetURL}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
"content_type": "snippets",
"file_name": filepath.Base(snippetURL),
"id": fmt.Sprintf("local:snippets/%s", filepath.Base(snippetURL)),
}),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -126,14 +126,14 @@ func TestAccResourceFile(t *testing.T) {
path = "{{.FileISO}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
"content_type": "iso",
"file_name": filepath.Base(fileISO),
"id": fmt.Sprintf("local:iso/%s", filepath.Base(fileISO)),
}),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -150,7 +150,7 @@ func TestAccResourceFile(t *testing.T) {
ExpectError: regexp.MustCompile("please specify .* - not both"),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -162,7 +162,7 @@ func TestAccResourceFile(t *testing.T) {
ExpectError: regexp.MustCompile("failed to determine file name from the URL"),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -171,7 +171,7 @@ func TestAccResourceFile(t *testing.T) {
},
// Do not allow to overwrite the file
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -184,7 +184,7 @@ func TestAccResourceFile(t *testing.T) {
},
// Allow to overwrite the file by default
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -192,7 +192,7 @@ func TestAccResourceFile(t *testing.T) {
path = "{{.SnippetFile2}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
"content_type": "snippets",
"file_name": filepath.Base(snippetFile2),
"id": fmt.Sprintf("local:snippets/%s", filepath.Base(snippetFile2)),
@ -203,7 +203,7 @@ func TestAccResourceFile(t *testing.T) {
PreConfig: func() {
deleteSnippet(te, filepath.Base(snippetFile1))
},
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "test" {
datastore_id = "local"
node_name = "{{.NodeName}}"
@ -211,7 +211,7 @@ func TestAccResourceFile(t *testing.T) {
path = "{{.SnippetFile1}}"
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_file.test", map[string]string{
"content_type": "snippets",
"file_name": filepath.Base(snippetFile1),
"id": fmt.Sprintf("local:snippets/%s", filepath.Base(snippetFile1)),
@ -278,9 +278,9 @@ func createFile(t *testing.T, namePattern string, content string) *os.File {
return f
}
func deleteSnippet(te *testEnvironment, fname string) {
func deleteSnippet(te *Environment, fname string) {
te.t.Helper()
err := te.nodeStorageClient().DeleteDatastoreFile(context.Background(), fmt.Sprintf("snippets/%s", fname))
err := te.NodeStorageClient().DeleteDatastoreFile(context.Background(), fmt.Sprintf("snippets/%s", fname))
require.NoError(te.t, err)
}

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"testing"
@ -13,14 +13,14 @@ import (
)
func TestAccResourceClusterFirewall(t *testing.T) {
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
steps []resource.TestStep
}{
{"rules1", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_firewall_rules" "rules1" {
rule {
type = "in"
@ -32,7 +32,7 @@ func TestAccResourceClusterFirewall(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_firewall_rules.rules1", map[string]string{
ResourceAttributes("proxmox_virtual_environment_firewall_rules.rules1", map[string]string{
"rule.0.type": "in",
"rule.0.action": "ACCEPT",
"rule.0.iface": "vmbr0",
@ -40,7 +40,7 @@ func TestAccResourceClusterFirewall(t *testing.T) {
"rule.0.proto": "tcp",
"rule.0.comment": "PVE Admin Interface",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_firewall_rules.rules1", []string{
NoResourceAttributesSet("proxmox_virtual_environment_firewall_rules.rules1", []string{
"node_name",
}),
),
@ -50,7 +50,7 @@ func TestAccResourceClusterFirewall(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"testing"
@ -15,7 +15,7 @@ import (
func TestAccResourceTime(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
@ -23,29 +23,29 @@ func TestAccResourceTime(t *testing.T) {
}{
{"change timezone", []resource.TestStep{
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
node_name = "{{.NodeName}}"
time_zone = "America/New_York"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
"time_zone": "America/New_York",
}),
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
node_name = "{{.NodeName}}"
time_zone = "UTC"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
"time_zone": "UTC",
}),
},
{
Config: te.renderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
Config: te.RenderConfig(`resource "proxmox_virtual_environment_time" "node_time" {
node_name = "{{.NodeName}}"
time_zone = "UTC"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_time.node_time", map[string]string{
"time_zone": "UTC",
}),
},
@ -55,7 +55,7 @@ func TestAccResourceTime(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"testing"
@ -15,14 +15,14 @@ import (
func TestAccResourceVM(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
step []resource.TestStep
}{
{"multiline description", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm1" {
node_name = "{{.NodeName}}"
started = false
@ -34,13 +34,13 @@ func TestAccResourceVM(t *testing.T) {
EOT
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm1", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm1", map[string]string{
"description": "my\ndescription\nvalue",
}),
),
}}},
{"single line description", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm2" {
node_name = "{{.NodeName}}"
started = false
@ -48,13 +48,13 @@ func TestAccResourceVM(t *testing.T) {
description = "my description value"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm2", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm2", map[string]string{
"description": "my description value",
}),
),
}}},
{"no description", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm3" {
node_name = "{{.NodeName}}"
started = false
@ -62,14 +62,14 @@ func TestAccResourceVM(t *testing.T) {
description = ""
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm3", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm3", map[string]string{
"description": "",
}),
),
}}},
{
"protection", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm4" {
node_name = "{{.NodeName}}"
started = false
@ -77,12 +77,12 @@ func TestAccResourceVM(t *testing.T) {
protection = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm4", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm4", map[string]string{
"protection": "true",
}),
),
}, {
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm4" {
node_name = "{{.NodeName}}"
started = false
@ -90,7 +90,7 @@ func TestAccResourceVM(t *testing.T) {
protection = false
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm4", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm4", map[string]string{
"protection": "false",
}),
),
@ -98,7 +98,7 @@ func TestAccResourceVM(t *testing.T) {
},
{
"update cpu block", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm5" {
node_name = "{{.NodeName}}"
started = false
@ -108,12 +108,12 @@ func TestAccResourceVM(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm5", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm5", map[string]string{
"cpu.0.sockets": "1",
}),
),
}, {
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm5" {
node_name = "{{.NodeName}}"
started = false
@ -123,7 +123,7 @@ func TestAccResourceVM(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm5", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm5", map[string]string{
"cpu.0.sockets": "1",
}),
),
@ -131,7 +131,7 @@ func TestAccResourceVM(t *testing.T) {
},
{
"update memory block", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm6" {
node_name = "{{.NodeName}}"
started = false
@ -141,12 +141,12 @@ func TestAccResourceVM(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm6", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm6", map[string]string{
"memory.0.dedicated": "2048",
}),
),
}, {
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm6" {
node_name = "{{.NodeName}}"
started = false
@ -156,7 +156,7 @@ func TestAccResourceVM(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm6", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm6", map[string]string{
"memory.0.dedicated": "1024",
}),
),
@ -169,7 +169,7 @@ func TestAccResourceVM(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.step,
})
})
@ -177,27 +177,27 @@ func TestAccResourceVM(t *testing.T) {
}
func TestAccResourceVMInitialization(t *testing.T) {
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
step []resource.TestStep
}{
{"custom cloud-init: use SCSI interface", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "cloud_config" {
content_type = "snippets"
datastore_id = "local"
node_name = "{{.NodeName}}"
source_raw {
data = <<-EOF
#cloud-config
runcmd:
- apt update
- apt install -y qemu-guest-agent
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
EOF
#cloud-config
runcmd:
- apt update
- apt install -y qemu-guest-agent
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
EOF
file_name = "cloud-config.yaml"
}
}
@ -247,7 +247,7 @@ func TestAccResourceVMInitialization(t *testing.T) {
}`),
}}},
{"native cloud-init: do not upgrade packages", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_cloudinit3" {
node_name = "{{.NodeName}}"
started = false
@ -256,7 +256,7 @@ func TestAccResourceVMInitialization(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_cloudinit3", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm_cloudinit3", map[string]string{
"initialization.0.upgrade": "false",
}),
),
@ -266,7 +266,7 @@ func TestAccResourceVMInitialization(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.step,
})
})
@ -274,27 +274,27 @@ func TestAccResourceVMInitialization(t *testing.T) {
}
func TestAccResourceVMNetwork(t *testing.T) {
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
step []resource.TestStep
}{
{"network interfaces", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_file" "cloud_config" {
content_type = "snippets"
datastore_id = "local"
node_name = "{{.NodeName}}"
source_raw {
data = <<EOF
#cloud-config
runcmd:
- apt update
- apt install -y qemu-guest-agent
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
EOF
data = <<-EOF
#cloud-config
runcmd:
- apt update
- apt install -y qemu-guest-agent
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
EOF
file_name = "cloud-config.yaml"
}
}
@ -341,7 +341,7 @@ EOF
overwrite_unmanaged = true
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_network1", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm_network1", map[string]string{
"ipv4_addresses.#": "2",
"mac_addresses.#": "2",
"network_device.0.bridge": "vmbr0",
@ -350,7 +350,7 @@ EOF
),
}}},
{"network device disconnected", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_network2" {
node_name = "{{.NodeName}}"
started = false
@ -360,13 +360,13 @@ EOF
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_network2", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm_network2", map[string]string{
"network_device.0.bridge": "vmbr0",
"network_device.0.disconnected": "false",
}),
),
}, {
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_network2" {
node_name = "{{.NodeName}}"
started = false
@ -377,7 +377,7 @@ EOF
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_network2", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_vm_network2", map[string]string{
"network_device.0.bridge": "vmbr0",
"network_device.0.disconnected": "true",
}),
@ -390,7 +390,7 @@ EOF
t.Parallel()
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.step,
})
})
@ -400,7 +400,7 @@ EOF
func TestAccResourceVMDisks(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
te := InitEnvironment(t)
tests := []struct {
name string
@ -408,7 +408,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}{
{"create disk with default parameters, then update it", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk1" {
node_name = "{{.NodeName}}"
started = false
@ -423,7 +423,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_disk1", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_disk1", map[string]string{
"disk.0.aio": "io_uring",
"disk.0.backup": "true",
"disk.0.cache": "none",
@ -441,7 +441,7 @@ func TestAccResourceVMDisks(t *testing.T) {
),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk1" {
node_name = "{{.NodeName}}"
started = false
@ -464,7 +464,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_disk1", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_disk1", map[string]string{
"disk.0.aio": "native",
"disk.0.backup": "true",
"disk.0.cache": "none",
@ -487,7 +487,7 @@ func TestAccResourceVMDisks(t *testing.T) {
},
}},
{"create disk from an image", []resource.TestStep{{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "test_disk2_image" {
content_type = "iso"
datastore_id = "local"
@ -509,7 +509,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_disk2", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_disk2", map[string]string{
"disk.0.cache": "none",
"disk.0.datastore_id": "local-lvm",
"disk.0.discard": "on",
@ -524,7 +524,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}}},
{"clone default disk without overrides", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk3_template" {
node_name = "{{.NodeName}}"
started = false
@ -550,7 +550,7 @@ func TestAccResourceVMDisks(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
// fully cloned disk, does not have any attributes in state
resource.TestCheckNoResourceAttr("proxmox_virtual_environment_vm.test_disk3", "disk.0"),
testResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{}),
ResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{}),
),
},
{
@ -559,7 +559,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}},
{"multiple disks", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk4" {
node_name = "{{.NodeName}}"
started = false
@ -579,7 +579,7 @@ func TestAccResourceVMDisks(t *testing.T) {
size = 8
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_vm.test_disk4", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disk4", map[string]string{
"disk.0.interface": "virtio0",
"disk.0.path_in_datastore": `vm-\d+-disk-1`,
"disk.1.interface": "scsi0",
@ -593,7 +593,7 @@ func TestAccResourceVMDisks(t *testing.T) {
{"cdrom", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_cdrom" {
node_name = "{{.NodeName}}"
started = false
@ -603,7 +603,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_cdrom", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_cdrom", map[string]string{
"cdrom.0.enabled": "true",
}),
),
@ -614,7 +614,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}},
{"efi disk", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_efi_disk" {
node_name = "{{.NodeName}}"
started = false
@ -626,7 +626,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_efi_disk", map[string]string{
ResourceAttributes("proxmox_virtual_environment_vm.test_efi_disk", map[string]string{
"efi_disk.0.datastore_id": "local-lvm",
"efi_disk.0.type": "4m",
}),
@ -638,7 +638,7 @@ func TestAccResourceVMDisks(t *testing.T) {
}},
{"ide disks", []resource.TestStep{
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disks" {
node_name = "{{.NodeName}}"
started = false
@ -651,13 +651,13 @@ func TestAccResourceVMDisks(t *testing.T) {
size = 8
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_vm.test_disks", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disks", map[string]string{
"disk.0.interface": "ide0",
"disk.0.path_in_datastore": `vm-\d+-disk-0`,
}),
},
{
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disks" {
node_name = "{{.NodeName}}"
started = false
@ -676,7 +676,7 @@ func TestAccResourceVMDisks(t *testing.T) {
size = 8
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_vm.test_disks", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disks", map[string]string{
"disk.#": "2",
}),
},
@ -690,7 +690,7 @@ func TestAccResourceVMDisks(t *testing.T) {
// this test is failing because of https://github.com/bpg/terraform-provider-proxmox/issues/873
return true, nil
},
Config: te.renderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk3_template" {
node_name = "{{.NodeName}}"
started = false
@ -721,7 +721,7 @@ func TestAccResourceVMDisks(t *testing.T) {
//size = 10
}
}`),
Check: testResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{
"disk.0.datastore_id": "local-lvm",
"disk.0.discard": "on",
"disk.0.file_format": "raw",
@ -737,6 +737,49 @@ func TestAccResourceVMDisks(t *testing.T) {
Destroy: false,
},
}},
{"clone with disk resize", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_disk3_template" {
node_name = "{{.NodeName}}"
started = false
name = "test-disk3-template"
template = "true"
disk {
file_format = "raw"
datastore_id = "local-lvm"
interface = "virtio0"
size = 8
}
}
resource "proxmox_virtual_environment_vm" "test_disk3" {
node_name = "{{.NodeName}}"
started = false
name = "test-disk3"
clone {
vm_id = proxmox_virtual_environment_vm.test_disk3_template.id
}
disk {
datastore_id = "local-lvm"
interface = "virtio0"
size = 10
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{
"disk.0.datastore_id": "local-lvm",
"disk.0.interface": "virtio0",
"disk.0.size": "10",
}),
),
},
{
RefreshState: true,
},
}},
}
for _, tt := range tests {
@ -744,7 +787,7 @@ func TestAccResourceVMDisks(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})

View File

@ -1,4 +1,4 @@
package tests
package test
import (
"bytes"
@ -17,29 +17,31 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/fwprovider"
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
sdkV2provider "github.com/bpg/terraform-provider-proxmox/proxmoxtf/provider"
"github.com/bpg/terraform-provider-proxmox/fwprovider"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/storage"
"github.com/bpg/terraform-provider-proxmox/utils"
)
type testEnvironment struct {
// Environment is a test environment for acceptance tests.
type Environment struct {
t *testing.T
templateVars map[string]any
providerConfig string
nodeName string
datastoreID string
NodeName string
DatastoreID string
accProviders map[string]func() (tfprotov6.ProviderServer, error)
AccProviders map[string]func() (tfprotov6.ProviderServer, error)
once sync.Once
c api.Client
}
func initTestEnvironment(t *testing.T) *testEnvironment {
// InitEnvironment initializes a new test environment for acceptance tests.
func InitEnvironment(t *testing.T) *Environment {
t.Helper()
nodeName := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_NAME")
@ -75,7 +77,7 @@ provider "proxmox" {
const datastoreID = "local"
return &testEnvironment{
return &Environment{
t: t,
templateVars: map[string]any{
"ProviderConfig": pc,
@ -83,23 +85,23 @@ provider "proxmox" {
"DatastoreID": datastoreID,
},
providerConfig: pc,
nodeName: nodeName,
datastoreID: datastoreID,
accProviders: muxProviders(t),
NodeName: nodeName,
DatastoreID: datastoreID,
AccProviders: muxProviders(t),
}
}
// addTemplateVars adds the given variables to the template variables of the current test environment.
// AddTemplateVars adds the given variables to the template variables of the current test environment.
// Please note that NodeName and ProviderConfig are reserved keys, they are set by the test environment
// and cannot be overridden.
func (e *testEnvironment) addTemplateVars(vars map[string]any) {
func (e *Environment) AddTemplateVars(vars map[string]any) {
for k, v := range vars {
e.templateVars[k] = v
}
}
// renderConfig renders the given configuration with for the current test environment using template engine.
func (e *testEnvironment) renderConfig(cfg string) string {
// RenderConfig renders the given configuration with for the current test environment using template engine.
func (e *Environment) RenderConfig(cfg string) string {
tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg)
require.NoError(e.t, err)
@ -110,7 +112,8 @@ func (e *testEnvironment) renderConfig(cfg string) string {
return buf.String()
}
func (e *testEnvironment) client() api.Client {
// Client returns a new API client for the test environment.
func (e *Environment) Client() api.Client {
if e.c == nil {
e.once.Do(
func() {
@ -139,16 +142,19 @@ func (e *testEnvironment) client() api.Client {
return e.c
}
func (e *testEnvironment) accessClient() *access.Client {
return &access.Client{Client: e.client()}
// AccessClient returns a new access client for the test environment.
func (e *Environment) AccessClient() *access.Client {
return &access.Client{Client: e.Client()}
}
func (e *testEnvironment) nodeClient() *nodes.Client {
return &nodes.Client{Client: e.client(), NodeName: e.nodeName}
// NodeClient returns a new nodes client for the test environment.
func (e *Environment) NodeClient() *nodes.Client {
return &nodes.Client{Client: e.Client(), NodeName: e.NodeName}
}
func (e *testEnvironment) nodeStorageClient() *storage.Client {
return &storage.Client{Client: e.nodeClient(), StorageName: e.datastoreID}
// NodeStorageClient returns a new storage client for the test environment.
func (e *Environment) NodeStorageClient() *storage.Client {
return &storage.Client{Client: e.NodeClient(), StorageName: e.DatastoreID}
}
// testAccMuxProviders returns a map of mux servers for the acceptance tests.

View File

@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
package test
import (
"fmt"
@ -14,7 +14,8 @@ import (
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func testResourceAttributes(res string, attrs map[string]string) resource.TestCheckFunc {
// ResourceAttributes is a helper function to test resource attributes.
func ResourceAttributes(res string, attrs map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for k, v := range attrs {
if v == "" {
@ -43,7 +44,8 @@ func testResourceAttributes(res string, attrs map[string]string) resource.TestCh
}
}
func testNoResourceAttributesSet(res string, attrs []string) resource.TestCheckFunc {
// NoResourceAttributesSet is a helper function to test that no resource attributes are set.
func NoResourceAttributesSet(res string, attrs []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, k := range attrs {
if err := resource.TestCheckNoResourceAttr(res, k)(s); err != nil {
@ -55,7 +57,8 @@ func testNoResourceAttributesSet(res string, attrs []string) resource.TestCheckF
}
}
func testResourceAttributesSet(res string, attrs []string) resource.TestCheckFunc {
// ResourceAttributesSet is a helper function to test that all resource attributes are set.
func ResourceAttributesSet(res string, attrs []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, k := range attrs {
if err := resource.TestCheckResourceAttrSet(res, k)(s); err != nil {

View File

@ -1,455 +0,0 @@
/*
* 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 tests
import (
"regexp"
"strconv"
"testing"
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceVM2(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
vmID := gofakeit.IntRange(90000, 100000)
te.addTemplateVars(map[string]any{
"VMID": vmID,
})
tests := []struct {
name string
steps []resource.TestStep
}{
{"create minimal VM", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"node_name": te.nodeName,
}),
testResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"id",
}),
),
}}},
{"create minimal VM with ID", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.VMID}}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"node_name": te.nodeName,
"id": strconv.Itoa(vmID),
}),
),
}}},
{"set an invalid VM name", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "not a valid DNS name"
}`),
ExpectError: regexp.MustCompile(`name must be a valid DNS name`),
}}},
{"set, update, import with primitive fields", []resource.TestStep{
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-vm"
description = "test description"
}`),
Check: testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"name": "test-vm",
"description": "test description",
}),
},
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-vm"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"name": "test-vm",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"description",
}),
),
},
{
ResourceName: "proxmox_virtual_environment_vm2.test_vm",
ImportState: true,
ImportStateVerify: true,
ImportStateIdPrefix: te.nodeName + "/",
},
}},
{"set, update, import with tags", []resource.TestStep{
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = ["tag2", "tag1"]
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag2"),
),
},
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = ["tag1"]
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
),
},
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
// no tags
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
),
},
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = []
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "0"),
),
},
}},
{"a VM can't have empty tags", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
tags = ["", "tag1"]
}`),
ExpectError: regexp.MustCompile(`string length must be at least 1, got: 0`),
}}},
{"a VM can't have empty tags", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
tags = [" ", "tag1"]
}`),
ExpectError: regexp.MustCompile(`must be a non-empty and non-whitespace string`),
}}},
{"multiline description", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
description = trimspace(<<-EOT
my
description
value
EOT
)
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"description": "my\ndescription\nvalue",
}),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
Steps: tt.steps,
})
})
}
}
func TestAccResourceVM2CPU(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
tests := []struct {
name string
steps []resource.TestStep
}{
{"create VM with no cpu params", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
// default values that are set by PVE if not specified
"cpu.cores": "1",
"cpu.sockets": "1",
"cpu.type": "kvm64",
}),
),
}}},
{"create VM with some cpu params", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.flags.#": "1",
"cpu.flags.0": `\+aes`,
}),
),
}}},
{"create VM with all cpu params and then update them", []resource.TestStep{
{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
# affinity = "0-1" only root can set affinity
# architecture = "x86_64" only root can set architecture
cores = 2
hotplugged = 2
limit = 64
numa = false
sockets = 2
type = "host"
units = 1024
flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.hotplugged": "2",
"cpu.limit": "64",
"cpu.numa": "false",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.units": "1024",
}),
),
},
{ // now update the cpu params and check if they are updated
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
cores = 4
hotplugged = 2
limit = null # setting to null is the same as removal
# numa = false
# sockets = 2 remove sockets, so it should fall back to 1 (PVE default)
# type = "host" remove type, so it should fall back to kvm64 (PVE default)
units = 2048
# flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "4",
"cpu.hotplugged": "2",
"cpu.sockets": "1", // default value, but it is a special case.
"cpu.type": "kvm64", // default value, but it is a special case.
"cpu.units": "2048",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"cpu.limit", // other defaults are not set in the state
"cpu.numa",
"cpu.flags",
}),
),
},
{
RefreshState: true,
},
}},
{"clone VM with some cpu params", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
name = "template-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
}
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.sockets": "2",
"cpu.type": "host",
}),
),
}}},
{"clone VM with some cpu params and updating them in the clone", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
name = "template-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
}
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
}
cpu = {
cores = 4
units = 1024
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "4",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.units": "1024",
}),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
Steps: tt.steps,
})
})
}
}
func TestAccResourceVM2Clone(t *testing.T) {
t.Parallel()
te := initTestEnvironment(t)
vmID := gofakeit.IntRange(90000, 100000)
te.addTemplateVars(map[string]any{
"VMID": vmID,
})
tests := []struct {
name string
steps []resource.TestStep
}{
{"create a clone from template", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "template"
description = "template description"
template = true
}
resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}"
name = "clone"
clone = {
id = proxmox_virtual_environment_vm2.test_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"template": "true",
}),
testResourceAttributes("proxmox_virtual_environment_vm2.test_vm_clone", map[string]string{
// name is overwritten
"name": "clone",
}),
testNoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm_clone", []string{
// description is not copied
"description",
}),
),
}}},
{"tags are copied to the clone", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
template = true
tags = ["tag1", "tag2"]
}
resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}"
clone = {
id = proxmox_virtual_environment_vm2.test_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm_clone", "tags.*", "tag1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm_clone", "tags.*", "tag2"),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
Steps: tt.steps,
})
})
}
}

View File

@ -2,110 +2,19 @@ package cpu
import (
"context"
"fmt"
"reflect"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
var (
_ basetypes.ObjectTypable = Type{}
_ basetypes.ObjectValuable = Value{}
)
// Type is an attribute type that represents CPU settings.
type Type struct {
basetypes.ObjectType
}
// String returns a human-readable representation of the type.
func (t Type) String() string {
return "cpu.Type"
}
// ValueFromObject returns a Value given a basetypes.ObjectValue.
func (t Type) ValueFromObject(
_ context.Context,
in basetypes.ObjectValue,
) (basetypes.ObjectValuable, diag.Diagnostics) {
value := Value{
Object: in,
}
return value, nil
}
// ValueFromTerraform returns a Value given a tftypes.Value.
// Value embeds the types.Object value returned from calling ValueFromTerraform on the
// types.ObjectType embedded in Type.
func (t Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
val, err := t.ObjectType.ValueFromTerraform(ctx, in)
if err != nil {
return nil, fmt.Errorf("failed to convert value to types.Object: %w", err)
}
obj, ok := val.(types.Object)
if !ok {
return nil, fmt.Errorf("%T cannot be used as types.Object", val)
}
return Value{obj}, nil
}
// ValueType returns the associated Value type for debugging.
func (t Type) ValueType(context.Context) attr.Value {
// It does not need to be a fully valid implementation of the type.
return Value{}
}
// Equal returns true if `candidate` is also a Type and has the same
// AttributeTypes.
func (t Type) Equal(candidate attr.Type) bool {
other, ok := candidate.(Type)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
// Value represents an object containing values to be used as CPU settings.
type Value struct {
types.Object
}
// Equal returns true if the Value is considered semantically equal
// (same type and same value) to the attr.Value passed as an argument.
func (v Value) Equal(c attr.Value) bool {
other, ok := c.(Value)
if !ok {
return false
}
return v.Object.Equal(other.Object)
}
// ToObjectValue returns the underlying ObjectValue.
func (v Value) ToObjectValue(_ context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
return v.Object, nil
}
// Type returns a Type with the same attribute types as `t`.
func (v Value) Type(ctx context.Context) attr.Type {
return Type{
types.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
// Value represents the type for CPU settings.
type Value = types.Object
// NewValue returns a new Value with the given CPU settings from the PVE API.
func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value {
@ -147,20 +56,20 @@ func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diag
obj, d := types.ObjectValueFrom(ctx, attributeTypes(), cpu)
diags.Append(d...)
return Value{obj}
return obj
}
// FillCreateBody fills the CreateRequestBody with the CPU settings from the Value.
//
// In the 'create' context, v is the plan.
func (v Value) FillCreateBody(ctx context.Context, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
var plan Model
if v.IsNull() || v.IsUnknown() {
if planValue.IsNull() || planValue.IsUnknown() {
return
}
d := v.Object.As(ctx, &plan, basetypes.ObjectAsOptions{})
d := planValue.As(ctx, &plan, basetypes.ObjectAsOptions{})
diags.Append(d...)
if d.HasError() {
@ -215,22 +124,22 @@ func (v Value) FillCreateBody(ctx context.Context, body *vms.CreateRequestBody,
// FillUpdateBody fills the UpdateRequestBody with the CPU settings from the Value.
//
// In the 'update' context, v is the plan and stateValue is the current state.
func (v Value) FillUpdateBody(
func FillUpdateBody(
ctx context.Context,
stateValue Value,
planValue, stateValue Value,
updateBody *vms.UpdateRequestBody,
isClone bool,
diags *diag.Diagnostics,
) {
var plan, state Model
if v.IsNull() || v.IsUnknown() || v.Equal(stateValue) {
if planValue.IsNull() || planValue.IsUnknown() || planValue.Equal(stateValue) {
return
}
d := v.Object.As(ctx, &plan, basetypes.ObjectAsOptions{})
d := planValue.As(ctx, &plan, basetypes.ObjectAsOptions{})
diags.Append(d...)
d = stateValue.Object.As(ctx, &state, basetypes.ObjectAsOptions{})
d = stateValue.As(ctx, &state, basetypes.ObjectAsOptions{})
diags.Append(d...)
if diags.HasError() {

View File

@ -18,10 +18,8 @@ import (
// Schema defines the schema for the CPU resource.
func Schema() schema.Attribute {
return schema.SingleNestedAttribute{
CustomType: Type{
ObjectType: basetypes.ObjectType{
AttrTypes: attributeTypes(),
},
CustomType: basetypes.ObjectType{
AttrTypes: attributeTypes(),
},
Description: "The CPU configuration.",
Optional: true,

View File

@ -0,0 +1,190 @@
package cpu_test
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
)
func TestAccResourceVM2CPU(t *testing.T) {
t.Parallel()
te := test.InitEnvironment(t)
tests := []struct {
name string
steps []resource.TestStep
}{
{"create VM with no cpu params", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
// default values that are set by PVE if not specified
"cpu.cores": "1",
"cpu.sockets": "1",
"cpu.type": "kvm64",
}),
),
}}},
{"create VM with some cpu params", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.flags.#": "1",
"cpu.flags.0": `\+aes`,
}),
),
}}},
{"create VM with all cpu params and then update them", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
# affinity = "0-1" only root can set affinity
# architecture = "x86_64" only root can set architecture
cores = 2
hotplugged = 2
limit = 64
numa = false
sockets = 2
type = "host"
units = 1024
flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.hotplugged": "2",
"cpu.limit": "64",
"cpu.numa": "false",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.units": "1024",
}),
),
},
{ // now update the cpu params and check if they are updated
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
cpu = {
cores = 4
hotplugged = 2
limit = null # setting to null is the same as removal
# numa = false
# sockets = 2 remove sockets, so it should fall back to 1 (PVE default)
# type = "host" remove type, so it should fall back to kvm64 (PVE default)
units = 2048
# flags = ["+aes"]
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "4",
"cpu.hotplugged": "2",
"cpu.sockets": "1", // default value, but it is a special case.
"cpu.type": "kvm64", // default value, but it is a special case.
"cpu.units": "2048",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"cpu.limit", // other defaults are not set in the state
"cpu.numa",
"cpu.flags",
}),
),
},
{
RefreshState: true,
},
}},
{"clone VM with some cpu params", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
name = "template-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
}
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "2",
"cpu.sockets": "2",
"cpu.type": "host",
}),
),
}}},
{"clone VM with some cpu params and updating them in the clone", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
name = "template-cpu"
cpu = {
cores = 2
sockets = 2
type = "host"
}
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
}
cpu = {
cores = 4
units = 1024
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"cpu.cores": "4",
"cpu.sockets": "2",
"cpu.type": "host",
"cpu.units": "1024",
}),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})
}
}

View File

@ -146,7 +146,7 @@ func (r *Resource) create(ctx context.Context, plan Model, diags *diag.Diagnosti
}
// fill out create body fields with values from other resource blocks
plan.CPU.FillCreateBody(ctx, createBody, diags)
cpu.FillCreateBody(ctx, plan.CPU, createBody, diags)
if diags.HasError() {
return
@ -320,7 +320,7 @@ func (r *Resource) update(ctx context.Context, plan, state Model, isClone bool,
}
}
plan.CPU.FillUpdateBody(ctx, state.CPU, updateBody, isClone, diags)
cpu.FillUpdateBody(ctx, plan.CPU, state.CPU, updateBody, isClone, diags)
if !updateBody.IsEmpty() {
updateBody.VMID = int(plan.ID.ValueInt64())

View File

@ -9,11 +9,13 @@ import (
)
// Model represents the VM model.
//
// Note: for computed fields / blocks we have to use an Object type (or an alias),
// or a custom type in order to hold an unknown value.
type Model struct {
Description types.String `tfsdk:"description"`
// for computed fields / blocks we have to use custom type? (because of unknown?)
CPU cpu.Value `tfsdk:"cpu"`
Clone *struct {
CPU cpu.Value `tfsdk:"cpu"`
Clone *struct {
ID types.Int64 `tfsdk:"id"`
Retries types.Int64 `tfsdk:"retries"`
} `tfsdk:"clone"`

View File

@ -0,0 +1,276 @@
/*
* 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 vm_test
import (
"regexp"
"strconv"
"testing"
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
)
func TestAccResourceVM(t *testing.T) {
t.Parallel()
te := test.InitEnvironment(t)
vmID := gofakeit.IntRange(90000, 100000)
te.AddTemplateVars(map[string]any{
"VMID": vmID,
})
tests := []struct {
name string
steps []resource.TestStep
}{
{"create minimal VM", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"node_name": te.NodeName,
}),
test.ResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"id",
}),
),
}}},
{"create minimal VM with ID", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.VMID}}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"node_name": te.NodeName,
"id": strconv.Itoa(vmID),
}),
),
}}},
{"set an invalid VM name", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "not a valid DNS name"
}`),
ExpectError: regexp.MustCompile(`name must be a valid DNS name`),
}}},
{"set, update, import with primitive fields", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-vm"
description = "test description"
}`),
Check: test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"name": "test-vm",
"description": "test description",
}),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-vm"
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"name": "test-vm",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"description",
}),
),
},
{
ResourceName: "proxmox_virtual_environment_vm2.test_vm",
ImportState: true,
ImportStateVerify: true,
ImportStateIdPrefix: te.NodeName + "/",
},
}},
{"set, update, import with tags", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = ["tag2", "tag1"]
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag2"),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = ["tag1"]
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
// no tags
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm", "tags.*", "tag1"),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-tags"
tags = []
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm2.test_vm", "tags.#", "0"),
),
},
}},
{"a VM can't have empty tags", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
tags = ["", "tag1"]
}`),
ExpectError: regexp.MustCompile(`string length must be at least 1, got: 0`),
}}},
{"a VM can't have empty tags", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
tags = [" ", "tag1"]
}`),
ExpectError: regexp.MustCompile(`must be a non-empty and non-whitespace string`),
}}},
{"multiline description", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
description = trimspace(<<-EOT
my
description
value
EOT
)
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"description": "my\ndescription\nvalue",
}),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})
}
}
func TestAccResourceVM2Clone(t *testing.T) {
t.Parallel()
te := test.InitEnvironment(t)
vmID := gofakeit.IntRange(90000, 100000)
te.AddTemplateVars(map[string]any{
"VMID": vmID,
})
tests := []struct {
name string
steps []resource.TestStep
}{
{"create a clone from template", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "template"
description = "template description"
template = true
}
resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}"
name = "clone"
clone = {
id = proxmox_virtual_environment_vm2.test_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"template": "true",
}),
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm_clone", map[string]string{
// name is overwritten
"name": "clone",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm_clone", []string{
// description is not copied
"description",
}),
),
}}},
{"tags are copied to the clone", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
template = true
tags = ["tag1", "tag2"]
}
resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}"
clone = {
id = proxmox_virtual_environment_vm2.test_vm.id
}
}`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm_clone", "tags.*", "tag1"),
resource.TestCheckTypeSetElemAttr("proxmox_virtual_environment_vm2.test_vm_clone", "tags.*", "tag2"),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})
}
}

View File

@ -14,12 +14,10 @@ description: |-
-> Many attributes are marked as **optional** _and_ **computed** in the schema,
hence you may seem added to the plan with "(known after apply)" status, even if they are not set in the configuration.
This is done to support the `clone` operation, when a VM is created from an existing one,
and attributes of the original VM are copied to the new one.<br><br>
This is done to support the `clone` operation, when a VM is created from an existing VM or template,
and the source attributes are copied to the clone.<br><br>
Computed attributes allow the provider to set those attributes without user input.
The attributes are marked as optional to allow the user to set (or overwrite) them if needed.
In order to remove the computed attribute from the plan, you can set it to an empty value (e.g. `""` for string, `[]` for collection).
The attributes are also marked as optional to allow the practitioner to set (or overwrite) them if needed.
{{ if .HasExample -}}
## Example Usage