Files
coder/coderd/promoauth/github.go
Steven Masley 13359aa16f chore: drop github per user rate limit tracking (#12286)
* chore: drop github per user rate limit tracking

Rate limits for authenticated requests are per user.
This would be an excessive number of prometheus labels,
so we only track the unauthorized limit.
2024-02-23 11:17:52 -06:00

102 lines
2.4 KiB
Go

package promoauth
import (
"net/http"
"strconv"
"time"
"golang.org/x/xerrors"
)
type rateLimits struct {
Limit int
Remaining int
Used int
Reset time.Time
Resource string
}
// githubRateLimits returns rate limit information from a GitHub response.
// GitHub rate limits are on a per-user basis, and tracking each user as
// a prometheus label might be too much. So only track rate limits for
// unauthorized responses.
//
// Unauthorized responses have a much stricter rate limit of 60 per hour.
// Tracking this is vital to ensure we do not hit the limit.
func githubRateLimits(resp *http.Response, err error) (rateLimits, bool) {
if err != nil || resp == nil {
return rateLimits{}, false
}
// Only track 401 responses which indicates we are using the 60 per hour
// rate limit.
if resp.StatusCode != http.StatusUnauthorized {
return rateLimits{}, false
}
p := headerParser{header: resp.Header}
// See
// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
limits := rateLimits{
Limit: p.int("x-ratelimit-limit"),
Remaining: p.int("x-ratelimit-remaining"),
Used: p.int("x-ratelimit-used"),
Resource: p.string("x-ratelimit-resource") + "-unauthorized",
}
if limits.Limit == 0 &&
limits.Remaining == 0 &&
limits.Used == 0 {
// For some requests, github has no rate limit. In which case,
// it returns all 0s. We can just omit these.
return limits, false
}
// Reset is when the rate limit "used" will be reset to 0.
// If it's unix 0, then we do not know when it will reset.
// Change it to a zero time as that is easier to handle in golang.
unix := p.int("x-ratelimit-reset")
resetAt := time.Unix(int64(unix), 0)
if unix == 0 {
resetAt = time.Time{}
}
limits.Reset = resetAt
if len(p.errors) > 0 {
// If we are missing any headers, then do not try and guess
// what the rate limits are.
return limits, false
}
return limits, true
}
type headerParser struct {
errors map[string]error
header http.Header
}
func (p *headerParser) string(key string) string {
if p.errors == nil {
p.errors = make(map[string]error)
}
v := p.header.Get(key)
if v == "" {
p.errors[key] = xerrors.Errorf("missing header %q", key)
}
return v
}
func (p *headerParser) int(key string) int {
v := p.string(key)
if v == "" {
return -1
}
i, err := strconv.Atoi(v)
if err != nil {
p.errors[key] = err
}
return i
}