fix: follow-up fixes for conditional VCS requests (#27711)

Follow-ups from #27627 

- Memoizes `Config.Git()` with a mutex so the provider's ETag response
cache survives across calls. Only successful construction is cached;
errors are retried.
- Moves the HTTP client onto `Config.HTTPClient`, wired through
`ConvertConfig`, so `Git()` no longer takes a per-call argument that
would be silently ignored after memoization.
- `newGitHub` and `newGitLab` now return `(Provider, error)`,
eliminating the typed-nil-interface class in `gitprovider.New` rather
than the single instance.
- Gates the 304 branch on a `haveCached` flag instead of a nil body
check.
- Only caches bodies that decode successfully, preventing poisoned
entries.
- Keys the response cache on the full token digest rather than a
truncated prefix.
- Tests added: `TestConfigGitMemoizesProvider`,
`TestConfigGitRetriesOnConstructorError`,
`TestGitLabConstructorErrorReturnsNilInterface`,
`TestResponseCacheStore`,
`TestConditionalRequestReuse/MalformedResponseNotCached`;
`TestConvertYAML/CustomScopesAndEndpoint` now asserts
`Config.HTTPClient` wiring.

Follow-ups tracked in #28139, #28140, #28141, #28142.

> 🤖 Generated by Coder Agents on behalf of @johnstcn.
This commit is contained in:
Cian Johnston
2026-08-18 09:00:20 +01:00
committed by GitHub
parent b674d40d39
commit 6079c514ee
11 changed files with 273 additions and 52 deletions
+14 -14
View File
@@ -37,7 +37,7 @@ type githubProvider struct {
repositorySSHPathPattern *regexp.Regexp
}
func newGitHub(apiBaseURL string, httpClient *http.Client, clock quartz.Clock) *githubProvider {
func newGitHub(apiBaseURL string, httpClient *http.Client, clock quartz.Clock) (Provider, error) {
if apiBaseURL == "" {
apiBaseURL = defaultGitHubAPIBaseURL
}
@@ -72,7 +72,7 @@ func newGitHub(apiBaseURL string, httpClient *http.Client, clock quartz.Clock) *
repositorySSHPathPattern: regexp.MustCompile(
`^(?:ssh://)?git@` + escapedHost + `[:/]([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+?)(?:\.git)?/?$`,
),
}
}, nil
}
// deriveWebBaseURL converts a GitHub API base URL to the
@@ -412,12 +412,13 @@ func (g *githubProvider) decodeJSON(
// changed, which is cheaper than a full body and does not count
// against the primary REST rate limit.
cacheKey := responseCacheKey(requestURL, token)
var cachedBody []byte
if g.cache != nil {
if etag, body, ok := g.cache.load(cacheKey); ok {
req.Header.Set("If-None-Match", etag)
cachedBody = body
}
var (
cachedBody []byte
haveCached bool
)
if etag, body, ok := g.cache.load(cacheKey); ok {
req.Header.Set("If-None-Match", etag)
cachedBody, haveCached = body, true
}
resp, err := g.httpClient.Do(req)
@@ -427,7 +428,7 @@ func (g *githubProvider) decodeJSON(
defer resp.Body.Close()
// Nothing changed since the cached response: reuse the stored body.
if resp.StatusCode == http.StatusNotModified && cachedBody != nil {
if resp.StatusCode == http.StatusNotModified && haveCached {
if err := json.Unmarshal(cachedBody, dest); err != nil {
return xerrors.Errorf("decode cached github response: %w", err)
}
@@ -457,14 +458,13 @@ func (g *githubProvider) decodeJSON(
return xerrors.Errorf("read github response: %w", err)
}
// Cache the validator so the next poll can be made conditional.
if g.cache != nil {
g.cache.store(cacheKey, resp.Header.Get("ETag"), body)
}
if err := json.Unmarshal(body, dest); err != nil {
return xerrors.Errorf("decode github response: %w", err)
}
// Only cache bodies we could successfully decode, so a malformed
// response does not poison the cache.
g.cache.store(cacheKey, resp.Header.Get("ETag"), body)
return nil
}