0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

fix(vm): vga block defaults handling during create / clone (#1732)

* fix(vm): fix `vga` block defaults handling during create / clone

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

* fix: formatting

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

* fix: acc test failure due to skip

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2025-01-30 22:11:46 -05:00 committed by GitHub
parent 4d89d3484b
commit c992dfc1f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 154 additions and 116 deletions

5
.gitignore vendored
View File

@ -46,5 +46,6 @@ id_rsa.pub
# Binary # Binary
terraform-provider-proxmox* terraform-provider-proxmox*
# .vscode # VScode / Cursor
.vscode/settings.json .vscode/settings.json
.cursorrules

11
.vscode/launch.json vendored
View File

@ -1,12 +1,21 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Run Acceptance Tests",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}/fwprovider/test",
"envFile": "${workspaceFolder}/testacc.env",
"args": ["-test.v", "-test.timeout", "120s"]
},
{ {
"name": "Debug Acceptance Tests", "name": "Debug Acceptance Tests",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "test", "mode": "test",
"program": "${workspaceFolder}/fwprovider/tests", "program": "${workspaceFolder}/fwprovider/test",
"envFile": "${workspaceFolder}/testacc.env", "envFile": "${workspaceFolder}/testacc.env",
"args": ["-debug", "-test.v", "-test.timeout", "120s"] "args": ["-debug", "-test.v", "-test.timeout", "120s"]

View File

@ -127,6 +127,14 @@
] ]
} }
], ],
"psi-header.lang-config": [
{
"language": "go",
"ignoreLines": [
"//go:build"
]
}
],
"makefile.configureOnOpen": false, "makefile.configureOnOpen": false,
"go.buildFlags": ["-tags=all"], "go.buildFlags": ["-tags=all"],
"gopls": { "build.buildFlags": ["-tags=all"] } "gopls": { "build.buildFlags": ["-tags=all"] }

View File

@ -311,7 +311,7 @@ func TestAccResourceStandardRepoValidInput(t *testing.T) {
// PUT /api2/json/nodes/{node}/apt/repositories with handle = "no-subscription" will create a new // PUT /api2/json/nodes/{node}/apt/repositories with handle = "no-subscription" will create a new
// entry in /etc/apt/sources.list on each call :/ // entry in /etc/apt/sources.list on each call :/
SkipFunc: func() (bool, error) { SkipFunc: func() (bool, error) {
return true, fmt.Errorf("skipped due to API limitation: PUT request creates new entry on each call") return true, nil
}, },
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_apt_standard_repository" "test" { resource "proxmox_virtual_environment_apt_standard_repository" "test" {
@ -337,8 +337,10 @@ func TestAccResourceStandardRepoValidInput(t *testing.T) {
// Test the "ImportState" implementation. // Test the "ImportState" implementation.
{ {
// PUT /api2/json/nodes/{node}/apt/repositories with handle = "no-subscription" will create a new
// entry in /etc/apt/sources.list on each call :/
SkipFunc: func() (bool, error) { SkipFunc: func() (bool, error) {
return true, fmt.Errorf("skipped due to API limitation: PUT request creates new entry on each call") return true, nil
}, },
ImportState: true, ImportState: true,
ImportStateId: fmt.Sprintf("%s,no-subscription", strings.ToLower(te.NodeName)), ImportStateId: fmt.Sprintf("%s,no-subscription", strings.ToLower(te.NodeName)),

View File

@ -9,7 +9,6 @@
package test package test
import ( import (
"math/rand"
"regexp" "regexp"
"testing" "testing"
@ -73,18 +72,16 @@ func TestAccResourceVM(t *testing.T) {
}), }),
), ),
}}}, }}},
{ {"empty node_name", []resource.TestStep{{
"empty node_name", []resource.TestStep{{ Config: te.RenderConfig(`
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_empty_node_name" { resource "proxmox_virtual_environment_vm" "test_empty_node_name" {
node_name = "" node_name = ""
started = false started = false
}`), }`),
ExpectError: regexp.MustCompile(`expected "node_name" to not be an empty string, got `), ExpectError: regexp.MustCompile(`expected "node_name" to not be an empty string, got `),
}}, }}},
}, {"protection", []resource.TestStep{
{ {
"protection", []resource.TestStep{{
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm4" { resource "proxmox_virtual_environment_vm" "test_vm4" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -110,10 +107,10 @@ func TestAccResourceVM(t *testing.T) {
"protection": "false", "protection": "false",
}), }),
), ),
}}, },
}, }},
{ {"update cpu block", []resource.TestStep{
"update cpu block", []resource.TestStep{{ {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm5" { resource "proxmox_virtual_environment_vm" "test_vm5" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -143,10 +140,10 @@ func TestAccResourceVM(t *testing.T) {
"cpu.0.sockets": "1", "cpu.0.sockets": "1",
}), }),
), ),
}}, },
}, }},
{ {"set cpu.architecture as non root is not supported", []resource.TestStep{
"set cpu.architecture as non root is not supported", []resource.TestStep{{ {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_cpu_arch" { resource "proxmox_virtual_environment_vm" "test_cpu_arch" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -157,8 +154,8 @@ func TestAccResourceVM(t *testing.T) {
}`, WithAPIToken()), }`, WithAPIToken()),
ExpectError: regexp.MustCompile(`can only be set by the root account`), ExpectError: regexp.MustCompile(`can only be set by the root account`),
}, },
{ {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "template" { resource "proxmox_virtual_environment_vm" "template" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
started = false started = false
@ -166,11 +163,11 @@ func TestAccResourceVM(t *testing.T) {
architecture = "x86_64" architecture = "x86_64"
} }
}`, WithRootUser()), }`, WithRootUser()),
Destroy: false, Destroy: false,
}}, },
}, }},
{ {"update memory block", []resource.TestStep{
"update memory block", []resource.TestStep{{ {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm6" { resource "proxmox_virtual_environment_vm" "test_vm6" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -200,10 +197,39 @@ func TestAccResourceVM(t *testing.T) {
"memory.0.dedicated": "1024", "memory.0.dedicated": "1024",
}), }),
), ),
}}, },
}, }},
{ {"create vga block", []resource.TestStep{
"update vga block", []resource.TestStep{{ {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
"vga.#": "0",
}),
),
}, {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
vga {
type = "virtio-gl"
clipboard = "vnc"
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
"vga.#": "1",
}),
),
},
}},
{"update vga block", []resource.TestStep{
{
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" { resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -253,10 +279,10 @@ func TestAccResourceVM(t *testing.T) {
"vga.0.clipboard": "", "vga.0.clipboard": "",
}), }),
), ),
}}, },
}, }},
{ {"update watchdog block", []resource.TestStep{
"update watchdog block", []resource.TestStep{{ {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" { resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
@ -309,8 +335,8 @@ func TestAccResourceVM(t *testing.T) {
"watchdog.0.action": "reset", "watchdog.0.action": "reset",
}), }),
), ),
}}, },
}, }},
} }
for _, tt := range tests { for _, tt := range tests {
@ -498,8 +524,9 @@ func TestAccResourceVMNetwork(t *testing.T) {
}), }),
), ),
}}}, }}},
{"network device disconnected", []resource.TestStep{{ {"network device disconnected", []resource.TestStep{
Config: te.RenderConfig(` {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_network2" { resource "proxmox_virtual_environment_vm" "test_vm_network2" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
started = false started = false
@ -508,14 +535,14 @@ func TestAccResourceVMNetwork(t *testing.T) {
bridge = "vmbr0" bridge = "vmbr0"
} }
}`), }`),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
ResourceAttributes("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.bridge": "vmbr0",
"network_device.0.disconnected": "false", "network_device.0.disconnected": "false",
}), }),
), ),
}, { }, {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_network2" { resource "proxmox_virtual_environment_vm" "test_vm_network2" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
started = false started = false
@ -525,13 +552,14 @@ func TestAccResourceVMNetwork(t *testing.T) {
disconnected = true disconnected = true
} }
}`), }`),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
ResourceAttributes("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.bridge": "vmbr0",
"network_device.0.disconnected": "true", "network_device.0.disconnected": "true",
}), }),
), ),
}}}, },
}},
} }
for _, tt := range tests { for _, tt := range tests {
@ -552,9 +580,6 @@ func TestAccResourceVMClone(t *testing.T) {
} }
te := InitEnvironment(t) te := InitEnvironment(t)
te.AddTemplateVars(map[string]interface{}{
"TemplateVMID": 100000 + rand.Intn(99999),
})
tests := []struct { tests := []struct {
name string name string
@ -564,8 +589,8 @@ func TestAccResourceVMClone(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "template" { resource "proxmox_virtual_environment_vm" "template" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
vm_id = {{.TemplateVMID}}
started = false started = false
template = true
cpu { cpu {
architecture = "x86_64" architecture = "x86_64"
} }
@ -578,6 +603,25 @@ func TestAccResourceVMClone(t *testing.T) {
} }
}`, WithRootUser()), }`, WithRootUser()),
}}}, }}},
{"clone no vga block", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "template" {
node_name = "{{.NodeName}}"
started = false
}
resource "proxmox_virtual_environment_vm" "clone" {
node_name = "{{.NodeName}}"
started = false
clone {
vm_id = proxmox_virtual_environment_vm.template.vm_id
}
}`),
Check: resource.ComposeTestCheckFunc(
ResourceAttributes("proxmox_virtual_environment_vm.clone", map[string]string{
"vga.#": "0",
}),
),
}}},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -1398,15 +1398,6 @@ func VM() *schema.Resource {
Type: schema.TypeList, Type: schema.TypeList,
Description: "The VGA configuration", Description: "The VGA configuration",
Optional: true, Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkVGAClipboard: dvVGAClipboard,
mkVGAMemory: dvVGAMemory,
mkVGAType: dvVGAType,
},
}, nil
},
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
mkVGAClipboard: { mkVGAClipboard: {
@ -2128,11 +2119,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
} }
if len(vga) > 0 { if len(vga) > 0 {
vgaDevice, err := vmGetVGADeviceObject(d) vgaDevice := vmGetVGADeviceObject(d)
if err != nil {
return diag.FromErr(err)
}
updateBody.VGADevice = vgaDevice updateBody.VGADevice = vgaDevice
} }
@ -2531,10 +2518,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool)) tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool))
template := types.CustomBool(d.Get(mkTemplate).(bool)) template := types.CustomBool(d.Get(mkTemplate).(bool))
vgaDevice, err := vmGetVGADeviceObject(d) vgaDevice := vmGetVGADeviceObject(d)
if err != nil {
return diag.FromErr(err)
}
vmIDUntyped, hasVMID := d.GetOk(mkVMID) vmIDUntyped, hasVMID := d.GetOk(mkVMID)
vmID := vmIDUntyped.(int) vmID := vmIDUntyped.(int)
@ -3320,39 +3304,33 @@ func vmGetTagsString(d *schema.ResourceData) string {
return strings.Join(sanitizedTags, ";") return strings.Join(sanitizedTags, ";")
} }
func vmGetVGADeviceObject(d *schema.ResourceData) (*vms.CustomVGADevice, error) { func vmGetVGADeviceObject(d *schema.ResourceData) *vms.CustomVGADevice {
resource := VM() vga := d.Get(mkVGA).([]interface{})
if len(vga) > 0 && vga[0] != nil {
vgaBlock := vga[0].(map[string]interface{})
vgaClipboard := vgaBlock[mkVGAClipboard].(string)
vgaMemory := vgaBlock[mkVGAMemory].(int)
vgaBlock, err := structure.GetSchemaBlock( vgaType := vgaBlock[mkVGAType].(string)
resource,
d, vgaDevice := &vms.CustomVGADevice{}
[]string{mkVGA},
0, if vgaClipboard != "" {
true, vgaDevice.Clipboard = &vgaClipboard
) }
if err != nil {
return nil, fmt.Errorf("error getting VGA block: %w", err) if vgaMemory > 0 {
vgaDevice.Memory = ptr.Ptr(int64(vgaMemory))
}
if vgaType != "" {
vgaDevice.Type = &vgaType
}
return vgaDevice
} }
vgaClipboard := vgaBlock[mkVGAClipboard].(string) return nil
vgaMemory := vgaBlock[mkVGAMemory].(int)
vgaType := vgaBlock[mkVGAType].(string)
vgaDevice := &vms.CustomVGADevice{}
if vgaClipboard != "" {
vgaDevice.Clipboard = &vgaClipboard
}
if vgaMemory > 0 {
vgaDevice.Memory = ptr.Ptr(int64(vgaMemory))
}
if vgaType != "" {
vgaDevice.Type = &vgaType
}
return vgaDevice, nil
} }
func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
@ -4420,9 +4398,9 @@ func vmReadCustom(
vga[mkVGAType] = *vmConfig.VGADevice.Type vga[mkVGAType] = *vmConfig.VGADevice.Type
} }
} else { } else {
vga[mkVGAClipboard] = "" vga[mkVGAClipboard] = dvVGAClipboard
vga[mkVGAMemory] = 0 vga[mkVGAMemory] = dvVGAMemory
vga[mkVGAType] = "" vga[mkVGAType] = dvVGAType
} }
currentVGA := d.Get(mkVGA).([]interface{}) currentVGA := d.Get(mkVGA).([]interface{})
@ -5306,11 +5284,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
// Prepare the new VGA configuration. // Prepare the new VGA configuration.
if d.HasChange(mkVGA) { if d.HasChange(mkVGA) {
updateBody.VGADevice, e = vmGetVGADeviceObject(d) updateBody.VGADevice = vmGetVGADeviceObject(d)
if e != nil {
return diag.FromErr(e)
}
rebootRequired = true rebootRequired = true
} }