R-569: stack-lifecycle handlers pick their status by error KIND, not English words

stop/start/restart/update, remove and delete matched "protected", "not found", "not deployed",
"still running", "not orphaned" in err.Error(). New sentinels in internal/stacks
(stack_errors.go) carried by the producers in manager.go/delete.go via util.KindErrorf (message
bytes unchanged); api.stackOpStatusFor maps them. Tests: reworded-message table per family,
wiring check over all three handlers, producers keep kind + words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 21:36:54 +02:00
parent 9271f33359
commit d15ad105be
7 changed files with 203 additions and 47 deletions
+7 -6
View File
@@ -21,6 +21,7 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/crypto"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
)
// ContainerState represents the current state of a container.
@@ -1275,7 +1276,7 @@ func deepCopyStack(s *Stack) Stack {
func (m *Manager) StartStack(name string) error {
stack, ok := m.GetStack(name)
if !ok {
return fmt.Errorf("stack %q not found", name)
return util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
}
if stack.Deploying {
// R-634: a second `compose up -d` beside the deploy's own is the race that left apps running
@@ -1338,7 +1339,7 @@ func (m *Manager) StartStackServices(name string, services []string) error {
}
stack, ok := m.GetStack(name)
if !ok {
return fmt.Errorf("stack %q not found", name)
return util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
}
m.logger.Printf("[INFO] [stacks] Starting stack %s services only: %v", name, services)
@@ -1358,12 +1359,12 @@ func (m *Manager) StartStackServices(name string, services []string) error {
func (m *Manager) StopStack(name string) error {
if m.cfg.IsProtectedStack(name) {
return fmt.Errorf("stack %q is protected and cannot be stopped", name)
return util.KindErrorf(ErrProtectedStack, "stack %q is protected and cannot be stopped", name)
}
stack, ok := m.GetStack(name)
if !ok {
return fmt.Errorf("stack %q not found", name)
return util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
}
if stack.Deploying {
// R-634, the backstop for EVERY caller (backup, quiesce, restore, export, storage, the Stop
@@ -1393,7 +1394,7 @@ func (m *Manager) StopStack(name string) error {
func (m *Manager) RestartStack(name string) error {
stack, ok := m.GetStack(name)
if !ok {
return fmt.Errorf("stack %q not found", name)
return util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
}
if m.isDebug() {
@@ -1437,7 +1438,7 @@ func (m *Manager) RestartStack(name string) error {
func (m *Manager) GetLogs(name string, lines int) (string, error) {
stack, ok := m.GetStack(name)
if !ok {
return "", fmt.Errorf("stack %q not found", name)
return "", util.KindErrorf(ErrStackNotFound, "stack %q not found", name)
}
if lines <= 0 {