v0.282.0: after_setup (the app's own sign-up switch), close sign-up now (decision 49), probes read lists + done_status (R-715)
gates / gates (push) Successful in 27s
gates / gates (push) Successful in 27s
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:
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -47,6 +48,8 @@ type SetupDoneProbe struct {
|
||||
URL string `yaml:"url" json:"url"`
|
||||
Field string `yaml:"field" json:"field"`
|
||||
Done string `yaml:"done" json:"done"`
|
||||
// DoneStatus (v0.282.0, R-715): a non-200 HTTP status that itself means "set up" (gramps-web answers 405).
|
||||
DoneStatus int `yaml:"done_status,omitempty" json:"done_status,omitempty"`
|
||||
}
|
||||
|
||||
// Setup gate states.
|
||||
@@ -67,6 +70,8 @@ type SetupGateRecord struct {
|
||||
OpenedBy string `yaml:"opened_by,omitempty" json:"opened_by,omitempty"`
|
||||
// SignupOpenUntil (v0.281.0, decision 47): the household opened sign-up until this time (signup_block.go).
|
||||
SignupOpenUntil string `yaml:"signup_open_until,omitempty" json:"signup_open_until,omitempty"`
|
||||
// NativeLock (v0.282.0): the app's own sign-up switch — "applied", "lifted" (the window), or "" (never set).
|
||||
NativeLock string `yaml:"native_lock,omitempty" json:"native_lock,omitempty"`
|
||||
}
|
||||
|
||||
// Closed reports whether the gate stands.
|
||||
@@ -325,6 +330,10 @@ func (m *Manager) OpenSetupGate(name, by string) error {
|
||||
m.logger.Printf("[ERROR] [stacks] %s: setup gate opened but its traefik file could not be removed (%v) — the reconcile retries", name, err)
|
||||
}
|
||||
m.logger.Printf("[INFO] [stacks] %s: setup gate OPENED by %s — the app is reached as without a gate", name, by)
|
||||
// v0.282.0 (decision 47): the app's own sign-up switch, after the block is up and the gate is down. One restart.
|
||||
if st.Meta.AfterSetup != nil {
|
||||
m.goNativeLock(name, true, "the gate opened ("+by+")")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -347,33 +356,62 @@ func (m *Manager) SetupGateHost(host string) (name string, closed bool, found bo
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
// setupGateProbeGet reads a probe URL (a seam: tests never reach a network).
|
||||
var setupGateProbeGet = func(url string) ([]byte, error) {
|
||||
// setupGateProbeFetch reads a probe URL: the HTTP status and the body (a seam: tests never reach a network).
|
||||
var setupGateProbeFetch = func(url string) (int, []byte, error) {
|
||||
c := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := c.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
b, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
return resp.StatusCode, b, err
|
||||
}
|
||||
|
||||
// probeSaysDone reads the field at the dotted path and compares its text form with done. Anything it cannot
|
||||
// read is "not done" — the gate stays closed (fail closed).
|
||||
// probeOnce asks the app's own status once. v0.282.0 (R-715): `done_status:` — an app that answers a fixed non-200
|
||||
// status once it is set up (gramps-web: 405 "Users already exist") — counts as done on that status alone; any other
|
||||
// non-200 is "cannot read" (fail closed). A 200 is read as JSON: `field` is a dotted path whose parts may be list
|
||||
// indexes (`setup.0.status` — ghost; `0.done` — home-assistant), compared as text with `done`.
|
||||
func probeOnce(p *SetupDoneProbe) (done bool, got string, err error) {
|
||||
status, body, err := setupGateProbeFetch(p.URL)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if p.DoneStatus != 0 && status == p.DoneStatus {
|
||||
return true, fmt.Sprintf("HTTP %d", status), nil
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return false, fmt.Sprintf("HTTP %d", status), fmt.Errorf("HTTP %d", status)
|
||||
}
|
||||
if p.Field == "" {
|
||||
return false, "no field", fmt.Errorf("the probe names no field")
|
||||
}
|
||||
done, got = probeSaysDone(body, p.Field, p.Done)
|
||||
return done, got, nil
|
||||
}
|
||||
|
||||
// probeSaysDone reads the field at the dotted path (a numeric part indexes a list) and compares its text form with
|
||||
// done. Anything it cannot read is "not done" — the gate stays closed (fail closed).
|
||||
func probeSaysDone(body []byte, field, done string) (bool, string) {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(body, &v); err != nil {
|
||||
return false, "not JSON"
|
||||
}
|
||||
for _, k := range strings.Split(field, ".") {
|
||||
obj, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return false, "no field " + field
|
||||
}
|
||||
if v, ok = obj[k]; !ok {
|
||||
switch cur := v.(type) {
|
||||
case map[string]interface{}:
|
||||
nv, ok := cur[k]
|
||||
if !ok {
|
||||
return false, "no field " + field
|
||||
}
|
||||
v = nv
|
||||
case []interface{}:
|
||||
n, err := strconv.Atoi(k)
|
||||
if err != nil || n < 0 || n >= len(cur) {
|
||||
return false, "no field " + field
|
||||
}
|
||||
v = cur[n]
|
||||
default:
|
||||
return false, "no field " + field
|
||||
}
|
||||
}
|
||||
@@ -431,14 +469,13 @@ func (m *Manager) SetupGateTick() {
|
||||
if it.probe == nil || it.probe.URL == "" || !it.running {
|
||||
continue
|
||||
}
|
||||
body, err := setupGateProbeGet(it.probe.URL)
|
||||
done, got, err := probeOnce(it.probe)
|
||||
if err != nil {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] %s: setup probe unreadable (%v) — gate stays closed", it.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
done, got := probeSaysDone(body, it.probe.Field, it.probe.Done)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] %s: setup probe %s = %q (done when %q)", it.name, it.probe.Field, got, it.probe.Done)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user