feat(breakglass): add halt-a-server op to the recovery console (§B4)
Add a root-gated "Halt a running server" operation to the break-glass console. The operator picks a server from the live fleet and the console flips that MinecraftServer CRD's spec.desiredState to Stopped via a spec-only merge patch, disjoint from the operator's status writes, so it cannot race or clobber reconciliation. It is the panel-independent emergency stop for when the box still has root plus a kubeconfig. System servers (login/lobby) are allowed but flagged: a system tag in the picker and an explicit WARNING in the post-exit summary, since halting login takes the shared auth front door down with no fallback. Audit is best-effort so a halt still works with the audit sink down. Already-stopped is a distinct no-op. The core (halt.go) is unit-tested against a real controller-runtime fake client that applies the patch.
This commit is contained in:
8 files changed
+649
-10
No files matched your search
+26
-1
@@ -139,10 +139,11 @@ type rootModel struct {
|
||||
adminHost string
|
||||
panelHost string
|
||||
accessAud string
|
||||
namespace string // minecraft workload namespace (cfg.K8s.Namespace); target of the halt op
|
||||
adminExists bool
|
||||
}
|
||||
|
||||
func newRootModel(ctx context.Context, store ownerStore, dbURL, rootDomain, adminHostname, panelHostname, accessAud, osUser string, adminExists bool, mode consoleMode) *rootModel {
|
||||
func newRootModel(ctx context.Context, store ownerStore, dbURL, rootDomain, adminHostname, panelHostname, accessAud, namespace, osUser string, adminExists bool, mode consoleMode) *rootModel {
|
||||
rm := &rootModel{
|
||||
ctx: ctx,
|
||||
reviewing: -1,
|
||||
@@ -153,6 +154,7 @@ func newRootModel(ctx context.Context, store ownerStore, dbURL, rootDomain, admi
|
||||
adminHost: adminHostname,
|
||||
panelHost: panelHostname,
|
||||
accessAud: accessAud,
|
||||
namespace: namespace,
|
||||
adminExists: adminExists,
|
||||
mode: mode,
|
||||
result: breakGlassResult{
|
||||
@@ -219,10 +221,33 @@ func (m *rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.op {
|
||||
case bgAddOperator:
|
||||
return m.adopt(newOperatorModel(m.ctx, m.store, m.osUser))
|
||||
case bgHaltServer:
|
||||
return m.adopt(newHaltModel(m.ctx, m.store, m.namespace, m.osUser))
|
||||
default:
|
||||
return m.adopt(newOwnerModel(m.ctx, m.store, m.osUser, m.adminExists))
|
||||
}
|
||||
|
||||
case haltResultMsg:
|
||||
// Halt is terminal in break-glass: record the durable summary (so cmdBreakGlass
|
||||
// can re-print it past the alt-screen teardown) and quit. A load/perform failure
|
||||
// routes through the root's error path; an empty fleet or cancel leaves halted
|
||||
// false, so the summary reports "no changes made".
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
return m, tea.Quit
|
||||
}
|
||||
if msg.done {
|
||||
m.result.halted = true
|
||||
m.result.haltServer = msg.outcome.name
|
||||
m.result.haltNamespace = msg.outcome.namespace
|
||||
m.result.haltAlreadyStopped = msg.outcome.alreadyStopped
|
||||
m.result.haltSystemServer = msg.outcome.system
|
||||
if msg.outcome.auditErr != nil {
|
||||
m.result.haltAuditWarning = msg.outcome.auditErr.Error()
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
|
||||
case ownerResultMsg:
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
|
||||
Reference in new issue
Block a user