Compare commits
No commits in common. "d0d8423ac6dcb69fc5056c5ab368465b81ff0124" and "8ef2ce01d0f7fd70475365c24737bb42c691ca21" have entirely different histories.
d0d8423ac6
...
8ef2ce01d0
5 changed files with 7 additions and 95 deletions
|
|
@ -418,10 +418,6 @@ func runClient(cfg config.Config) {
|
|||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
slog.Error("shutdown error", "err", err)
|
||||
}
|
||||
// Drain background cache work (revalidation kicked off on hits)
|
||||
// before exiting, so in-flight sidecar writes finish rather than
|
||||
// being abandoned mid-flight.
|
||||
cacheLayer.Wait()
|
||||
slog.Info("stopped")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,10 +120,8 @@ func walkAndIndex(idx *Index, fsRoot, dirAbs, serverDir string) error {
|
|||
func indexTransmittalFolder(idx *Index, fsRoot, folderAbs, folderServerPath, date string) error {
|
||||
return filepath.WalkDir(folderAbs, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
// Log and continue indexing the rest of the folder — a
|
||||
// permission or FS error on one entry shouldn't abort the
|
||||
// whole transmittal index or vanish without a trace.
|
||||
slog.Warn("transmittal index: walkdir error", "path", path, "err", err)
|
||||
// Log the error but continue indexing other files
|
||||
_ = err // would log here: slog.Warn("walkdir error", "path", path, "err", err)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
|
|
|
|||
35
zddc/internal/cache/cache.go
vendored
35
zddc/internal/cache/cache.go
vendored
|
|
@ -67,13 +67,6 @@ type Cache struct {
|
|||
|
||||
markerOnce sync.Once
|
||||
|
||||
// wg tracks background goroutines (cache revalidation on hits,
|
||||
// mirror-walk hooks) so Wait() can drain them. Without this they
|
||||
// outlive the request — fine in production until a graceful
|
||||
// shutdown wants them finished, and in tests they race t.TempDir
|
||||
// cleanup by writing into the cache root after the test returns.
|
||||
wg sync.WaitGroup
|
||||
|
||||
// onAccess is invoked (when non-nil) after a request is dispatched.
|
||||
// The walker scheduler installs this hook to kick mirror walks based
|
||||
// on incoming traffic. Always called in a goroutine — must not
|
||||
|
|
@ -92,25 +85,6 @@ type Cache struct {
|
|||
// offline writes.
|
||||
func (c *Cache) SetOutbox(o *Outbox) { c.outbox = o }
|
||||
|
||||
// goBackground runs fn in a tracked goroutine so Wait can drain
|
||||
// in-flight background work — cache revalidation kicked off on a hit,
|
||||
// and the mirror-walk access hook. These must never block the user
|
||||
// response, but they also shouldn't outlive a graceful shutdown (or,
|
||||
// in tests, a t.TempDir cleanup that they'd race by writing into the
|
||||
// cache root after the test returns).
|
||||
func (c *Cache) goBackground(fn func()) {
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait blocks until all tracked background goroutines have finished.
|
||||
// Intended for graceful shutdown; tests call it before the temp-dir
|
||||
// cleanup runs.
|
||||
func (c *Cache) Wait() { c.wg.Wait() }
|
||||
|
||||
// New constructs a Cache from the loaded configuration. Validates
|
||||
// upstream URL, reads the bearer-file (if configured), prepares the
|
||||
// HTTP client honoring SkipTLSVerify, and ensures the cache root
|
||||
|
|
@ -207,8 +181,7 @@ func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// after we've started serving — the user's request never blocks
|
||||
// on walk scheduling.
|
||||
if c.onAccess != nil {
|
||||
urlPath := r.URL.Path
|
||||
c.goBackground(func() { c.onAccess(urlPath) })
|
||||
go c.onAccess(r.URL.Path)
|
||||
}
|
||||
|
||||
// Directory listings: try sidecar listing-cache, fall back to
|
||||
|
|
@ -228,8 +201,7 @@ func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
if err == nil && !info.IsDir() {
|
||||
c.serveFromDisk(w, r, path, info, "hit")
|
||||
// Background revalidate; never block the user response.
|
||||
urlPath, mtime := r.URL.Path, info.ModTime()
|
||||
c.goBackground(func() { c.revalidate(urlPath, mtime) })
|
||||
go c.revalidate(r.URL.Path, info.ModTime())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -249,8 +221,7 @@ func (c *Cache) serveDirectory(w http.ResponseWriter, r *http.Request) {
|
|||
info, err := os.Stat(path)
|
||||
if err == nil && !info.IsDir() {
|
||||
c.serveListingFromDisk(w, r, path, info, "hit")
|
||||
urlPath, accept, mtime := r.URL.Path, r.Header.Get("Accept"), info.ModTime()
|
||||
c.goBackground(func() { c.revalidateListing(urlPath, accept, mtime) })
|
||||
go c.revalidateListing(r.URL.Path, r.Header.Get("Accept"), info.ModTime())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
zddc/internal/cache/cache_test.go
vendored
8
zddc/internal/cache/cache_test.go
vendored
|
|
@ -31,14 +31,6 @@ func newTestCache(t *testing.T, mode string, upstreamHandler http.HandlerFunc) (
|
|||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
// Drain background revalidation goroutines before the test's
|
||||
// t.TempDir cleanup runs. Cleanups fire LIFO and t.TempDir
|
||||
// registered its RemoveAll first (at the t.TempDir() call above),
|
||||
// so this runs before it — preventing a revalidate goroutine from
|
||||
// recreating the cache dir / dropping a temp file mid-RemoveAll
|
||||
// ("directory not empty"). The upstream stays up (its Close was
|
||||
// registered earliest, so it runs last).
|
||||
t.Cleanup(c.Wait)
|
||||
return c, upstream
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -459,53 +459,8 @@ func TestInvariant_UnelevatedAdminNoSilentBypass(t *testing.T) {
|
|||
|
||||
func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) {
|
||||
// These checks lock in the existence-hiding property: non-admins must
|
||||
// see 404, never 403, so they can't probe which admin-only resources
|
||||
// exist. ServeProfile is the dispatcher (the refactor this test waited
|
||||
// on); its adminOnly wrapper denies with 404 before the sub-handler
|
||||
// runs, so a nil ring/index is safe for the non-admin paths.
|
||||
cfg, _ := invariantsFixture(t)
|
||||
|
||||
adminEndpoints := []string{"/whoami", "/config", "/logs", "/effective-policy", "/reindex"}
|
||||
|
||||
profileGet := func(sub, email string, elevated bool) *httptest.ResponseRecorder {
|
||||
req := httptest.NewRequest(http.MethodGet, ProfilePathPrefix+sub, nil)
|
||||
ctx := context.WithValue(req.Context(), EmailKey, email)
|
||||
ctx = context.WithValue(ctx, ElevatedKey, elevated)
|
||||
req = req.WithContext(ctx)
|
||||
rec := httptest.NewRecorder()
|
||||
ServeProfile(cfg, nil, nil, rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Non-admin (eve, project_team only) and anonymous callers must get
|
||||
// 404 on every admin endpoint — never 403, never 200.
|
||||
for _, who := range []struct {
|
||||
email string
|
||||
elevated bool
|
||||
label string
|
||||
}{
|
||||
{"eve@example.com", false, "non-admin"},
|
||||
{"", false, "anonymous"},
|
||||
{"admin@example.com", false, "un-elevated admin"}, // sudo-style: no authority until elevated
|
||||
} {
|
||||
for _, sub := range adminEndpoints {
|
||||
rec := profileGet(sub, who.email, who.elevated)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Errorf("%s GET /.profile%s = %d, want 404 (existence-hiding)", who.label, sub, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Positive control: an elevated root admin must NOT get 404 on the
|
||||
// gated routes that need no ring/index — proving the 404s above are
|
||||
// the admin gate, not a missing route. (/whoami and /config don't
|
||||
// touch the log ring or archive index.)
|
||||
for _, sub := range []string{"/whoami", "/config"} {
|
||||
rec := profileGet(sub, "admin@example.com", true)
|
||||
if rec.Code == http.StatusNotFound {
|
||||
t.Errorf("elevated admin GET /.profile%s = 404; the gate should admit admins", sub)
|
||||
}
|
||||
}
|
||||
// see 404, never 403, so they can't probe which paths exist.
|
||||
t.Skip("requires the profile handler dispatcher entry point; skip until the refactor confirms ServeProfile signature")
|
||||
}
|
||||
|
||||
// dump prints the rec body when t.Logf would help debugging — used in
|
||||
|
|
|
|||
Loading…
Reference in a new issue