cli: feed state in carlos system geoip status (geoip spec §10)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+53
−3
internal/console/apisystemgeoip.go+12 −0system_geoip.go+24 −3system_geoip_test.go+17 −0
CI passed — run details
diff --git a/internal/console/apisystemgeoip.go b/internal/console/apisystemgeoip.go| index 7ef27ccb0..51ca9fb28 100644 |
| --- a/internal/console/apisystemgeoip.go |
| +++ b/internal/console/apisystemgeoip.go |
| @@ -13,6 +13,7 @@ import ( |
| "context" |
| "errors" |
| "fmt" |
| + "io" |
| "log" |
| "net/http" |
| "os" |
| @@ -169,6 +170,17 @@ func (s *Server) handleAPISystemGeoipGet(w http.ResponseWriter, r *http.Request) |
| ready, rows, blocking := geoipVerdict(want, inv, time.Now()) |
| out["boxes"], out["ready"], out["blocking"] = rows, ready, blocking |
| out["feed_set"] = os.Getenv("CONSOLE_GEOIP_FEED") != "" |
| + if raw, _, err := release.OpenObject(ctx, s.cfg.Releases, geoip.FeedKey); err == nil { |
| + b, rerr := io.ReadAll(io.LimitReader(raw, geoip.MaxPointerBytes+1)) |
| + raw.Close() |
| + if rerr == nil && len(b) <= geoip.MaxPointerBytes { |
| + if fs, err := geoip.ParseFeedStatus(b); err == nil { |
| + out["feed"] = map[string]any{"name": fs.Feed, "last_check": fs.LastCheck, "last_success": fs.LastSuccess, |
| + "edition": fs.LastEdition, "failures": fs.ConsecutiveFailures, "not_found": fs.ConsecutiveNotFound, |
| + "last_failure": fs.LastFailure, "failing": fs.Failing()} |
| + } |
| + } |
| + } |
| writeJSON(w, http.StatusOK, out) |
| } |
diff --git a/system_geoip.go b/system_geoip.go| index bcf440023..61c1fd7d2 100644 |
| --- a/system_geoip.go |
| +++ b/system_geoip.go |
| @@ -165,7 +165,15 @@ type geoipStatusResponse struct { |
| Attribution string `json:"attribution"` |
| } `json:"pointer"` |
| PointerError string `json:"pointer_error"` |
| - Boxes []struct { |
| + Feed *struct { |
| + Name string `json:"name"` |
| + LastSuccess time.Time `json:"last_success"` |
| + Failures int `json:"failures"` |
| + NotFound int `json:"not_found"` |
| + LastFailure string `json:"last_failure"` |
| + Failing bool `json:"failing"` |
| + } `json:"feed"` |
| + Boxes []struct { |
| Label string `json:"label"` |
| Fleet string `json:"fleet"` |
| State string `json:"state"` |
| @@ -215,8 +223,21 @@ func printGeoipStatus(w io.Writer, resp geoipStatusResponse, now time.Time) { |
| fmt.Fprintf(w, " pages that show this data must credit: %s\n", p.Attribution) |
| } |
| } |
| - if resp.FeedSet { |
| - fmt.Fprintln(w, "feed: set (CONSOLE_GEOIP_FEED)") |
| + switch f := resp.Feed; { |
| + case f != nil && (f.Failures > 0 || f.NotFound > 0): |
| + n := f.Failures |
| + if f.NotFound > n { |
| + n = f.NotFound |
| + } |
| + line := fmt.Sprintf("feed: %s, failing (%d in a row, last: %s)", f.Name, n, f.LastFailure) |
| + if f.Failing { |
| + line = c.bad(line) |
| + } |
| + fmt.Fprintln(w, line) |
| + case f != nil && !f.LastSuccess.IsZero(): |
| + fmt.Fprintf(w, "feed: %s, last success %s\n", f.Name, f.LastSuccess.Format("2006-01-02")) |
| + case resp.FeedSet: |
| + fmt.Fprintln(w, "feed: set (CONSOLE_GEOIP_FEED), not run yet") |
| } |
| fmt.Fprintln(w) |
| for _, b := range resp.Boxes { |
diff --git a/system_geoip_test.go b/system_geoip_test.go| index 057f6884e..ae1f3da1b 100644 |
| --- a/system_geoip_test.go |
| +++ b/system_geoip_test.go |
| @@ -126,3 +126,20 @@ func TestPrintGeoipOffWarnsAboutTheFeed(t *testing.T) { |
| t.Fatalf("off without a feed mentioned it: %q", out.String()) |
| } |
| } |
| + |
| +func TestPrintGeoipStatusFeedLine(t *testing.T) { |
| + var resp geoipStatusResponse |
| + json.Unmarshal([]byte(`{"feed":{"name":"dbip-city-lite","failures":3,"last_failure":"download"},"ready":true}`), &resp) |
| + var out bytes.Buffer |
| + printGeoipStatus(&out, resp, time.Now()) |
| + if !strings.Contains(out.String(), "feed: dbip-city-lite, failing (3 in a row, last: download)") { |
| + t.Fatalf("feed line = %q", out.String()) |
| + } |
| + json.Unmarshal([]byte(`{"feed":{"name":"dbip-city-lite","last_success":"2026-10-01T00:00:00Z"},"ready":true}`), &resp) |
| + resp.Feed.Failures = 0 |
| + out.Reset() |
| + printGeoipStatus(&out, resp, time.Now()) |
| + if !strings.Contains(out.String(), "feed: dbip-city-lite, last success 2026-10-01") { |
| + t.Fatalf("feed line = %q", out.String()) |
| + } |
| +} |