Power: say why a BMC command failed (CLI, UI); certificate changes are events
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 40s

Found in the lab test of certificate pinning: the CLI printed only
'ipmitool failed on 1 machine(s)'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-29 22:37:02 +01:00
1 parent 9ab081aa5a
commit d0ea29d2a9
4 files changed
+21 -5

No files matched your search

+5 -2
View File
@@ -232,13 +232,16 @@ func runClient(cmd string, args []string) error {
if len(ids) > 1 {
fmt.Printf("%s: ", id)
}
fmt.Println(str(d["output"]))
if o := str(d["output"]); o != "" {
fmt.Println(o)
}
if ok, _ := d["ok"].(bool); !ok {
fmt.Println("error:", orDefault(str(d["error"]), "the BMC command failed"))
failed++
}
}
if failed > 0 {
return fmt.Errorf("ipmitool failed on %d machine(s)", failed)
return fmt.Errorf("power %s failed on %d machine(s)", args[1], failed)
}
case "bootdev":
_ = need(2)
+10 -1
View File
@@ -72,7 +72,16 @@ func (a *App) power(ctx context.Context, m *Machine, op, arg string) (string, er
if c, err = a.pinBMC(ctx, m, c); err != nil {
return "", err
}
return a.powerVia(ctx, a.rackOf(m), c, op, arg)
out, err := a.powerVia(ctx, a.rackOf(m), c, op, arg)
a.noteCertChange(ctx, m, err)
return out, err
}
// noteCertChange puts a refused BMC certificate on the machine's events.
func (a *App) noteCertChange(ctx context.Context, m *Machine, err error) {
if err != nil && strings.Contains(err.Error(), "certificate changed") {
_ = a.store.AddEvent(ctx, m.ID, "bmc certificate changed", oneLine(err.Error()))
}
}
// pinBMC records the certificate of an HTTPS BMC (Redfish, Proxmox) before
+5 -1
View File
@@ -786,7 +786,11 @@ func (a *App) handlePower(w http.ResponseWriter, r *http.Request) {
if action != "status" {
_ = a.store.AddEvent(r.Context(), m.ID, "power "+action, out)
}
writeJSON(w, 200, obj{"id": m.ID, "action": action, "ok": err == nil, "output": out})
res := obj{"id": m.ID, "action": action, "ok": err == nil, "output": out}
if err != nil {
res["error"] = err.Error()
}
writeJSON(w, 200, res)
}
func (a *App) handleBootdev(w http.ResponseWriter, r *http.Request) {
+1 -1
View File
@@ -355,7 +355,7 @@ async function each(ids, label, fn) {
await refresh(true);
}
const act = {
power: (ids, action) => each(ids, `power ${action}`, async id => { const r = await api('POST', `/api/v1/machines/${id}/power`, { action }); return r.output || (r.ok ? 'ok' : 'failed'); }),
power: (ids, action) => each(ids, `power ${action}`, async id => { const r = await api('POST', `/api/v1/machines/${id}/power`, { action }); if (!r.ok) throw new Error(r.error || r.output || 'the BMC command failed'); return r.output || 'ok'; }),
bootdev: (ids, device) => each(ids, `bootdev ${device}`, async id => { const r = await api('POST', `/api/v1/machines/${id}/bootdev`, { device }); return r.output || (r.ok ? 'ok' : 'failed'); }),
async deploy(ids) {
const one = ids.length === 1 ? S.machines.find(m => m.id === ids[0]) : null;