carlos / platform Public

cli: feed state in carlos system geoip status (geoip spec §10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev ee3855fc7ca81714efc00f97243e95835644cdee parent 9f06776
3 files changed, +53 −3
  • internal/console/apisystemgeoip.go +12 −0
  • system_geoip.go +24 −3
  • system_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())
+ }
+}