hub v0.26.0: pull-based config delivery + retire inbound GUI controls
config_version counter (bumped on every config save) advertised in the report ACK; controller re-pulls + self-restarts on a change. Retire Trigger Update / Push Config / Pull Config / Show Diff handlers+routes+buttons and the inbound geo-notify (keep hub->Cloudflare geo removal). Setup command -> host-install; delete dead customer.html + config_detail.html. Closes AUDIT-hub-gui F-S1/F-S4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
)
|
||||
|
||||
// The report ACK advertises the per-customer config_version (v0.26.0). The controller compares it
|
||||
// against its last-applied version and re-pulls + self-restarts on a change.
|
||||
func TestReportACK_ConfigVersion(t *testing.T) {
|
||||
h, st, _ := newTestHandler(t)
|
||||
|
||||
if err := st.SaveCustomerConfig(&store.CustomerConfig{
|
||||
CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: "{}",
|
||||
}); err != nil {
|
||||
t.Fatalf("SaveCustomerConfig: %v", err)
|
||||
}
|
||||
|
||||
// First report: ACK carries the baseline version (1).
|
||||
rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"c"}`)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("report status = %d, want 200 (body=%s)", rr.Code, rr.Body.String())
|
||||
}
|
||||
var ack map[string]interface{}
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &ack); err != nil {
|
||||
t.Fatalf("decode ACK: %v", err)
|
||||
}
|
||||
cv, ok := ack["config_version"]
|
||||
if !ok {
|
||||
t.Fatalf("ACK missing config_version for a config-managed customer")
|
||||
}
|
||||
if cv.(float64) != 1 {
|
||||
t.Errorf("ACK config_version = %v, want 1 (baseline)", cv)
|
||||
}
|
||||
|
||||
// Edit the config → the version bumps → the next ACK carries the new version. This is what makes
|
||||
// the box re-pull + restart. RED-PROOF: if SaveCustomerConfig did NOT bump (the bump is dropped),
|
||||
// the ACK would still report 1 here and the box would never converge — this assertion fails.
|
||||
if err := st.SaveCustomerConfig(&store.CustomerConfig{
|
||||
CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: `{"git":{"username":"x"}}`,
|
||||
}); err != nil {
|
||||
t.Fatalf("SaveCustomerConfig (edit): %v", err)
|
||||
}
|
||||
rr = do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"c"}`)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("report status = %d, want 200", rr.Code)
|
||||
}
|
||||
json.Unmarshal(rr.Body.Bytes(), &ack)
|
||||
if ack["config_version"].(float64) != 2 {
|
||||
t.Errorf("ACK config_version after edit = %v, want 2", ack["config_version"])
|
||||
}
|
||||
}
|
||||
|
||||
// A report-only customer (no config row) gets no config_version field — it has nothing to pull, and
|
||||
// an old controller that ignores the field behaves exactly as before.
|
||||
func TestReportACK_ConfigVersionOmittedWhenNoConfig(t *testing.T) {
|
||||
h, _, _ := newTestHandler(t)
|
||||
|
||||
rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"nobody"}`)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("report status = %d, want 200", rr.Code)
|
||||
}
|
||||
var ack map[string]interface{}
|
||||
json.Unmarshal(rr.Body.Bytes(), &ack)
|
||||
if _, ok := ack["config_version"]; ok {
|
||||
t.Errorf("ACK should omit config_version for a report-only (no config) customer; got %v", ack["config_version"])
|
||||
}
|
||||
}
|
||||
@@ -291,6 +291,11 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
|
||||
if custCfg.Status == "blocked" {
|
||||
resp["customer_blocked"] = true
|
||||
}
|
||||
// Config-refresh (v0.26.0): advertise the per-customer config_version. The controller compares
|
||||
// it against its last-applied version and, on a change, re-pulls controller.yaml + self-restarts
|
||||
// (pull-based config delivery — the hub never connects into the box). Only emitted for
|
||||
// config-managed customers (a report-only box without a config row gets no field and is unaffected).
|
||||
resp["config_version"] = custCfg.ConfigVersion
|
||||
}
|
||||
|
||||
// Phase 2 managed updates: advertise the effective controller-version FLOOR (per-customer override
|
||||
|
||||
Reference in New Issue
Block a user