diff --git a/docs/common/caching.md b/docs/common/caching.md index 977377e..f74bd75 100644 --- a/docs/common/caching.md +++ b/docs/common/caching.md @@ -13,7 +13,7 @@ Every proxied request on a remote is classified as **denied**, **immutable** or | `mutable_patterns` | Regexes forcing a path mutable. | | `immutable_ttl` | Seconds an immutable path stays fresh. `0` = forever. | | `mutable_ttl` | Seconds a mutable path stays fresh. `0` also means never expires. Omitted on create, it defaults to `3600`; omitted on update, the stored value is kept. | -| `check_mutable` | When a mutable path's `mutable_ttl` expires, sends a conditional `HEAD` (`If-None-Match`) with the stored upstream ETag. A `304` re-arms the TTL and serves the stored copy; anything else refetches in full. The ETag is kept for `max(10 × mutable_ttl, 7 days)`, re-armed on each `304`, so idle paths age out. It is only stored when `check_mutable` is set and `mutable_ttl` is non-zero. | +| `check_mutable` | When a mutable path's `mutable_ttl` expires, sends a conditional `HEAD` (`If-None-Match`) with the stored upstream ETag. A `304` re-arms the TTL and serves the stored copy; anything else refetches in full. The ETag is kept for `max(10 × mutable_ttl, 7 days)`, re-armed on each `304`, so idle paths age out. It is only stored when `check_mutable` is set and `mutable_ttl` is non-zero, and never for immutable paths. | | `stale_on_error` | On an upstream network error, serve the stored copy instead of failing. | | `quarantine_enabled` / `quarantine_days` | Stored on the remote but not enforced by the proxy yet. | | `mirrorlist` / `mirror_strategy` | rpm/deb/alpine only. Upstream pool is `[base_url] + mirrorlist`; `round_robin` (default) rotates the starting mirror, `least_conn` starts at the one with the fewest in-flight requests. Network errors and 5xx fail over to the next mirror; other statuses return as-is. | diff --git a/internal/proxy/engine.go b/internal/proxy/engine.go index ed0efc6..2c8d497 100644 --- a/internal/proxy/engine.go +++ b/internal/proxy/engine.go @@ -521,9 +521,6 @@ func (e *Engine) fetchFromUpstreamOnce(ctx context.Context, remote models.Remote } _ = e.cache.SetTTL(ctx, remote.Name, path, ttl) - if etag := resp.Header.Get("ETag"); etag != "" { - _ = e.cache.SetETag(ctx, remote.Name, path, etag, ttl) - } reader, info, err := e.store.Download(ctx, casResult.S3Key) if err != nil { diff --git a/internal/proxy/engine_test.go b/internal/proxy/engine_test.go index e366d2e..b4cf1ff 100644 --- a/internal/proxy/engine_test.go +++ b/internal/proxy/engine_test.go @@ -354,6 +354,20 @@ func TestMutableETagStorage(t *testing.T) { } } +// Immutable paths are never revalidated, so their upstream ETag is not kept. +func TestImmutableFetchStoresNoETag(t *testing.T) { + requireStack(t) + srv, _, _ := changingUpstream(t) + r := seed(t, models.Remote{Name: "eng-etag-imm", PackageType: models.PackageRPM, RepoType: models.RepoTypeRemote, BaseURL: srv.URL, CheckMutable: true, MutableTTL: 60}) + const rpm = "Packages/e2e-1.0-1.noarch.rpm" + if body, src := mutableFetch(t, r, rpm); body != "rev1:/"+rpm || src != "remote" { + t.Fatalf("fetch = %q from %s", body, src) + } + if got := etagKeyTTL(t, r.Name, rpm); got != -2 { + t.Errorf("etag key ttl = %v, want no key", got) + } +} + // A 304 for a path whose S3 body is gone falls back to a full refetch. func TestNotModifiedWithEvictedBodyRefetches(t *testing.T) { requireStack(t) diff --git a/internal/proxy/evict_test.go b/internal/proxy/evict_test.go index 15b8def..622cc35 100644 --- a/internal/proxy/evict_test.go +++ b/internal/proxy/evict_test.go @@ -59,7 +59,7 @@ type cached struct{ row, index, ttl, etag bool } // Mutable indexes live in the S3 index; immutable blobs get an artifact row. var ( indexCached = cached{index: true, ttl: true, etag: true} - blobCached = cached{row: true, ttl: true, etag: true} + blobCached = cached{row: true, ttl: true} ) func layers(t *testing.T, remote, path string) cached {