diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b8e41bcc..48e30c15d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: - version: latest + version: 1.26.2 args: release --rm-dist env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} diff --git a/opennebula/resource_opennebula_virtual_machine.go b/opennebula/resource_opennebula_virtual_machine.go index 8efbe5fde..9a2311210 100644 --- a/opennebula/resource_opennebula_virtual_machine.go +++ b/opennebula/resource_opennebula_virtual_machine.go @@ -526,7 +526,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R // TODO: fix it after 5.10 release // Force the "decrypt" bool to false to keep ONE 5.8 behavior - vm, err := vmc.Info(false) + vmInfo, err := vmc.Info(false) if err != nil { if NoExists(err) { log.Printf("[WARN] Removing virtual machine %s from state because it no longer exists in", d.Get("name")) @@ -540,16 +540,21 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R }) return diags } - d.SetId(fmt.Sprintf("%v", vm.ID)) - d.Set("name", vm.Name) - d.Set("uid", vm.UID) - d.Set("gid", vm.GID) - d.Set("uname", vm.UName) - d.Set("gname", vm.GName) - d.Set("state", vm.StateRaw) - d.Set("lcmstate", vm.LCMStateRaw) + d.SetId(fmt.Sprintf("%v", vmInfo.ID)) + d.Set("name", vmInfo.Name) + d.Set("uid", vmInfo.UID) + d.Set("gid", vmInfo.GID) + d.Set("uname", vmInfo.UName) + d.Set("gname", vmInfo.GName) + d.Set("state", vmInfo.StateRaw) + d.Set("lcmstate", vmInfo.LCMStateRaw) + if vm.State(vmInfo.StateRaw) == vm.Done { + log.Printf("[WARN] Replacing virtual machine %s (id: %s) because VM is 'Done'; ", d.Get("name"), d.Id()) + d.SetId("") + return nil + } //TODO fix this: - err = d.Set("permissions", permissionsUnixString(*vm.Permissions)) + err = d.Set("permissions", permissionsUnixString(*vmInfo.Permissions)) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -560,7 +565,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R } if customVM != nil { - customDiags := customVM(ctx, d, vm) + customDiags := customVM(ctx, d, vmInfo) if len(customDiags) > 0 { return customDiags } @@ -571,7 +576,7 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R if inheritedVectorsIf != nil { inheritedVectors = inheritedVectorsIf.(map[string]interface{}) } - err = flattenTemplate(d, inheritedVectors, &vm.Template) + err = flattenTemplate(d, inheritedVectors, &vmInfo.Template) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -587,14 +592,14 @@ func resourceOpennebulaVirtualMachineReadCustom(ctx context.Context, d *schema.R inheritedTags = inheritedTagsIf.(map[string]interface{}) } - flattenDiags := flattenVMUserTemplate(d, meta, inheritedTags, &vm.UserTemplate.Template) + flattenDiags := flattenVMUserTemplate(d, meta, inheritedTags, &vmInfo.UserTemplate.Template) for _, diag := range flattenDiags { diag.Detail = fmt.Sprintf("virtual machine (ID: %s): %s", d.Id(), err) diags = append(diags, diag) } - if vm.LockInfos != nil { - d.Set("lock", LockLevelToString(vm.LockInfos.Locked)) + if vmInfo.LockInfos != nil { + d.Set("lock", LockLevelToString(vmInfo.LockInfos.Locked)) } return diags diff --git a/opennebula/resource_opennebula_virtual_machine_test.go b/opennebula/resource_opennebula_virtual_machine_test.go index b2e126c5b..a37035aed 100644 --- a/opennebula/resource_opennebula_virtual_machine_test.go +++ b/opennebula/resource_opennebula_virtual_machine_test.go @@ -2,10 +2,12 @@ package opennebula import ( "fmt" + "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/vm" "os" "reflect" "strconv" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -313,6 +315,36 @@ func TestAccVirtualMachinePending(t *testing.T) { }) } +func TestAccVirtualMachineDoneTriggerRecreation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVirtualMachineDone, + ExpectNonEmptyPlan: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("opennebula_virtual_machine.test", "name", "virtual_machine_done"), + testAccTerminateVM("virtual_machine_done"), + ), + }, + { + RefreshState: true, + ExpectNonEmptyPlan: true, + Check: resource.ComposeTestCheckFunc( + func(state *terraform.State) error { + if !state.Empty() && len(state.RootModule().Resources) != 0 { + return fmt.Errorf("expected state to be empty") + } + return nil + }, + ), + }, + }, + }) +} + func TestAccVirtualMachineResize(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -454,6 +486,52 @@ func testAccSetDSdummy() resource.TestCheckFunc { } } +func testAccTerminateVM(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Configuration) + controller := config.Controller + + id, err := controller.VMs().ByName(name) + if err != nil { + return err + } + + err = controller.VM(id).Terminate() + if err != nil { + return err + } + return waitForVMState(id, vm.Done, time.Minute*5) + } +} + +// waitForVMState waits until the VM with vmId has the desiredState. +// returns an error if timeout is reached. +func waitForVMState(vmId int, desiredState vm.State, timeout time.Duration) error { + config := testAccProvider.Meta().(*Configuration) + controller := config.Controller + interval := time.NewTicker(5 * time.Second) + deadline := time.NewTimer(timeout) + + // Ensure ticker and timer are stopped after use + defer interval.Stop() + defer deadline.Stop() + + for { + select { + case <-interval.C: + info, err := controller.VM(vmId).Info(false) + if err != nil { + return err + } + if vm.State(info.StateRaw) == desiredState { + return nil + } + case <-deadline.C: + return fmt.Errorf("timeout waiting for vm id '%d' to reach desired state %s\n", vmId, desiredState) + } + } +} + func testAccCheckVirtualMachinePermissions(expected *shared.Permissions) resource.TestCheckFunc { return func(s *terraform.State) error { config := testAccProvider.Meta().(*Configuration) @@ -861,6 +939,32 @@ resource "opennebula_virtual_machine" "test" { } ` +var testAccVirtualMachineDone = ` +resource "opennebula_virtual_machine" "test" { + name = "virtual_machine_done" + group = "oneadmin" + permissions = "642" + memory = 128 + cpu = 0.1 + + context = { + NETWORK = "YES" + SET_HOSTNAME = "$NAME" + } + + graphics { + type = "VNC" + listen = "0.0.0.0" + keymap = "en-us" + } + + os { + arch = "x86_64" + boot = "" + } +} +` + var testAccVirtualMachineTemplateAddvCPU = ` resource "opennebula_virtual_machine" "test" { name = "test-virtual_machine"