diff --git a/fwprovider/test/resource_vm_test.go b/fwprovider/test/resource_vm_test.go index 87377855..ae198179 100644 --- a/fwprovider/test/resource_vm_test.go +++ b/fwprovider/test/resource_vm_test.go @@ -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 { diff --git a/proxmox/nodes/vms/vms.go b/proxmox/nodes/vms/vms.go index 261cd99f..1d2624c9 100644 --- a/proxmox/nodes/vms/vms.go +++ b/proxmox/nodes/vms/vms.go @@ -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) diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go index 108115fc..4c4e8c3a 100644 --- a/proxmoxtf/resource/vm/vm.go +++ b/proxmoxtf/resource/vm/vm.go @@ -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,