From 40a73a8bbaea4fbc4ce783926622227aeaa82585 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:08:57 +1000 Subject: [PATCH] fix: project instance status state through vm.EC2APIState buildInstanceStatus read vm.EC2StateCodes directly, so a StateError instance surfaced the non-AWS name "error" while DescribeInstances returned "stopped" for the same instance. --- .../handlers/ec2/instance/service_impl.go | 2 +- .../ec2/instance/service_impl_test.go | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/spinifex/handlers/ec2/instance/service_impl.go b/spinifex/handlers/ec2/instance/service_impl.go index e1fcf1891..1fd50082a 100644 --- a/spinifex/handlers/ec2/instance/service_impl.go +++ b/spinifex/handlers/ec2/instance/service_impl.go @@ -2839,7 +2839,7 @@ func (s *InstanceServiceImpl) hostUnderMemoryPressure() bool { func (s *InstanceServiceImpl) buildInstanceStatus(v *vm.VM, systemImpaired bool) *ec2.InstanceStatus { state := &ec2.InstanceState{} - if info, ok := vm.EC2StateCodes[v.Status]; ok { + if info, ok := vm.EC2APIState(v.Status); ok { state.SetCode(info.Code) state.SetName(info.Name) } else { diff --git a/spinifex/handlers/ec2/instance/service_impl_test.go b/spinifex/handlers/ec2/instance/service_impl_test.go index 06d70a6e0..380292981 100644 --- a/spinifex/handlers/ec2/instance/service_impl_test.go +++ b/spinifex/handlers/ec2/instance/service_impl_test.go @@ -3721,6 +3721,43 @@ func TestDescribeInstanceStatus_IncludeAllSurfacesPending(t *testing.T) { assert.Equal(t, "not-applicable", *out.InstanceStatuses[0].SystemStatus.Status) } +// StateError has no AWS enum, so the status path must project it through +// vm.EC2APIState onto stopped rather than surfacing the internal "error" name. +func TestBuildInstanceStatus_ErrorStateProjectsToStopped(t *testing.T) { + owner := "111122223333" + errored := runningVM("i-err", owner) + errored.Status = vm.StateError + svc := instanceStatusService(t, "az-a", map[string]*vm.VM{errored.ID: errored}) + + is := svc.buildInstanceStatus(errored, false) + require.NotNil(t, is.InstanceState) + assert.Equal(t, "stopped", *is.InstanceState.Name) + assert.Equal(t, int64(80), *is.InstanceState.Code) +} + +// Every internal state must map onto a valid AWS InstanceStateName here, the +// same guarantee vm.EC2APIState gives the DescribeInstances projection. +func TestBuildInstanceStatus_NeverSurfacesNonAWSName(t *testing.T) { + owner := "111122223333" + valid := map[string]bool{ + "pending": true, "running": true, "shutting-down": true, + "terminated": true, "stopping": true, "stopped": true, + } + for _, state := range []vm.InstanceState{ + vm.StateProvisioning, vm.StatePending, vm.StateRunning, vm.StateStopping, + vm.StateStopped, vm.StateShuttingDown, vm.StateTerminated, vm.StateError, + } { + v := runningVM("i-"+string(state), owner) + v.Status = state + svc := instanceStatusService(t, "az-a", map[string]*vm.VM{v.ID: v}) + + is := svc.buildInstanceStatus(v, false) + require.NotNil(t, is.InstanceState) + assert.True(t, valid[*is.InstanceState.Name], + "buildInstanceStatus(%s) surfaced non-AWS name %q", state, *is.InstanceState.Name) + } +} + func TestDescribeInstanceStatus_IncludeAllSurfacesStopped(t *testing.T) { owner := "111122223333" stopped := runningVM("i-stop", owner)