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

feat(vm): do not force VM re-create on initialization.user_account changes (#1885)

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2025-04-07 18:56:59 -04:00 committed by GitHub
parent 51e6d7b8db
commit d631ccbf90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 27 deletions

View File

@ -13,6 +13,7 @@ import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/bpg/terraform-provider-proxmox/utils"
)
@ -479,19 +480,6 @@ func TestAccResourceVMInitialization(t *testing.T) {
overwrite_unmanaged = true
}`),
}}},
{"native cloud-init: do not upgrade packages", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_cloudinit3" {
node_name = "{{.NodeName}}"
started = false
initialization {
upgrade = false
}
}`),
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_vm_cloudinit3", map[string]string{
"initialization.0.upgrade": "false",
}),
}}},
{"native cloud-init: username should not change", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm_cloudinit4" {
@ -547,6 +535,36 @@ func TestAccResourceVMInitialization(t *testing.T) {
"initialization.0.user_account.0.password": `\*\*\*\*\*\*\*\*\*\*`,
}),
}}},
{"native cloud-init: username update should not cause replacement", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
initialization {
user_account {
username = "ubuntu"
password = "password"
}
}
}`),
}, {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm" "test_vm" {
node_name = "{{.NodeName}}"
started = false
initialization {
user_account {
username = "ubuntu-updated"
password = "password"
}
}
}`),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("proxmox_virtual_environment_vm.test_vm", plancheck.ResourceActionUpdate),
},
},
}}},
}
for _, tt := range tests {

View File

@ -290,6 +290,16 @@ func (c *Client) ListVMs(ctx context.Context) ([]*ListResponseData, error) {
return resBody.Data, nil
}
// RebuildCloudInitDisk regenerates and changes cloud-init config drive.
func (c *Client) RebuildCloudInitDisk(ctx context.Context) error {
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("cloudinit"), nil, nil)
if err != nil {
return fmt.Errorf("error rebuilding cloud-init drive: %w", err)
}
return nil
}
// RebootVM reboots a virtual machine.
func (c *Client) RebootVM(ctx context.Context, d *RebootRequestBody) error {
taskID, err := c.RebootVMAsync(ctx, d)

View File

@ -843,7 +843,6 @@ func VM() *schema.Resource {
Type: schema.TypeList,
Description: "The user account configuration",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
@ -853,14 +852,12 @@ func VM() *schema.Resource {
Type: schema.TypeList,
Description: "The SSH keys",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkInitializationUserAccountPassword: {
Type: schema.TypeString,
Description: "The SSH password",
Optional: true,
ForceNew: true,
Sensitive: true,
Default: dvInitializationUserAccountPassword,
DiffSuppressFunc: func(_, oldVal, _ string, _ *schema.ResourceData) bool {
@ -872,7 +869,6 @@ func VM() *schema.Resource {
Type: schema.TypeString,
Description: "The SSH username",
Optional: true,
ForceNew: true,
},
},
},
@ -4786,7 +4782,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
if d.HasChange(mkNodeName) {
migrateTimeoutSec := d.Get(mkTimeoutMigrate).(int)
ctx, cancel := context.WithTimeout(ctx, time.Duration(migrateTimeoutSec)*time.Second)
migrateCtx, cancel := context.WithTimeout(ctx, time.Duration(migrateTimeoutSec)*time.Second)
defer cancel()
oldNodeNameValue, _ := d.GetChange(mkNodeName)
@ -4800,7 +4796,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
OnlineMigration: &trueValue,
}
err := vmAPI.MigrateVM(ctx, migrateBody)
err := vmAPI.MigrateVM(migrateCtx, migrateBody)
if err != nil {
return diag.FromErr(err)
}
@ -5113,11 +5109,12 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
// Prepare the new cloud-init configuration.
stoppedBeforeUpdate := false
cloudInitRebuildRequired := false
if d.HasChange(mkInitialization) {
initializationConfig := vmGetCloudInitConfig(d)
cloudInitConfig := vmGetCloudInitConfig(d)
updateBody.CloudInitConfig = initializationConfig
updateBody.CloudInitConfig = cloudInitConfig
initialization := d.Get(mkInitialization).([]interface{})
@ -5158,12 +5155,12 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
if mustMove || mustChangeDatastore || existingInterface == "" {
// CloudInit must be moved, either from a device to another or from a datastore
// to another (or both). This requires the VM to be stopped.
if err := vmShutdown(ctx, vmAPI, d); err != nil {
return err
if er := vmShutdown(ctx, vmAPI, d); er != nil {
return er
}
if err := deleteIdeDrives(ctx, vmAPI, initializationInterface, existingInterface); err != nil {
return err
if er := deleteIdeDrives(ctx, vmAPI, initializationInterface, existingInterface); er != nil {
return er
}
stoppedBeforeUpdate = true
@ -5179,6 +5176,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
})
}
cloudInitRebuildRequired = true
rebootRequired = true
}
@ -5402,14 +5400,20 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
return diags
}
} else {
if e := vmShutdown(ctx, vmAPI, d); e != nil {
return e
if er := vmShutdown(ctx, vmAPI, d); er != nil {
return er
}
rebootRequired = false
}
}
if cloudInitRebuildRequired {
if er := vmAPI.RebuildCloudInitDisk(ctx); er != nil {
return diag.FromErr(err)
}
}
// Change the disk locations and/or sizes, if necessary.
return vmUpdateDiskLocationAndSize(
ctx,