mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add GitLab support to coderd/externalauth/gitprovider
Fixes CODAGT-146 Add GitLab support to the gitprovider package for gitsync/chatd PR diff flows. This is a squashed stack of 3 PRs: #25651 - refactor(coderd/externalauth): prepare gitprovider for multi-provider support - Change gitprovider.New to return (Provider, error) - Extract shared helpers (parseRetryAfter, checkRateLimitError, countDiffLines, escapePathPreserveSlashes) from github.go - Update all callers (db2sdk, exp_chats, gitsync) for new signature - Add error logging for provider construction failures - Thread context through provider resolution #25652 - feat(coderd/externalauth/gitprovider): add GitLab provider - Implement full Provider interface: FetchPullRequestStatus, FetchPullRequestDiff, FetchBranchDiff, ResolveBranchPullRequest - Handle nested groups, forks, and self-hosted instances - Rate limit detection on both library and raw HTTP paths - URL parsing/building with NormalizePullRequestURL support - Unit tests covering error paths, URL parsing, state mapping - Document GitLab configuration and known limitations #25653 - test(coderd/externalauth/gitprovider): add GitLab VCR integration tests - FetchPullRequestStatus: 4 fixtures (open, conflicts, merged, closed) - FetchPullRequestDiff: 4 fixtures - FetchBranchDiff: 3 fixtures (open, deleted, fork) - ResolveBranchPullRequest: 3 fixtures - go-vcr cassettes with sanitized GitLab API responses
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
@@ -102,11 +104,19 @@ type PRStatus struct {
|
||||
FetchedAt time.Time
|
||||
}
|
||||
|
||||
// trailingPunctuation is the set of characters stripped from the right
|
||||
// of a raw URL before parsing it as a pull request URL.
|
||||
const trailingPunctuation = "),;."
|
||||
|
||||
// MaxDiffSize is the maximum number of bytes read from a diff
|
||||
// response. Diffs exceeding this limit are rejected with
|
||||
// ErrDiffTooLarge.
|
||||
const MaxDiffSize = 4 << 20 // 4 MiB
|
||||
|
||||
// RateLimitPadding is added to rate-limit retry times to guard
|
||||
// against over-consumption of request quotas.
|
||||
const RateLimitPadding = 5 * time.Minute
|
||||
|
||||
// ErrDiffTooLarge is returned when a diff exceeds MaxDiffSize.
|
||||
var ErrDiffTooLarge = xerrors.Errorf("diff exceeds maximum size of %d bytes", MaxDiffSize)
|
||||
|
||||
@@ -169,9 +179,9 @@ type Provider interface {
|
||||
}
|
||||
|
||||
// New creates a Provider for the given provider type and API base
|
||||
// URL. Returns nil if the provider type is not a supported git
|
||||
// provider.
|
||||
func New(providerType string, apiBaseURL string, httpClient *http.Client, opts ...Option) Provider {
|
||||
// URL. Returns (nil, nil) for unsupported provider types and a
|
||||
// non-nil error if construction fails.
|
||||
func New(providerType string, apiBaseURL string, httpClient *http.Client, opts ...Option) (Provider, error) {
|
||||
o := providerOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
@@ -182,12 +192,71 @@ func New(providerType string, apiBaseURL string, httpClient *http.Client, opts .
|
||||
|
||||
switch providerType {
|
||||
case "github":
|
||||
return newGitHub(apiBaseURL, httpClient, o.clock)
|
||||
return newGitHub(apiBaseURL, httpClient, o.clock), nil
|
||||
case "gitlab":
|
||||
return newGitLab(apiBaseURL, httpClient, o.clock)
|
||||
default:
|
||||
// Other providers (gitlab, bitbucket-cloud, etc.) will be
|
||||
// Other providers (bitbucket-cloud, etc.) will be
|
||||
// added here as they are implemented.
|
||||
return nil, nil //nolint:nilnil // nil provider means unsupported type, not an error
|
||||
}
|
||||
}
|
||||
|
||||
// parseRetryAfter extracts a retry duration from rate-limit response
|
||||
// headers. It checks Retry-After (seconds) first, then the named
|
||||
// resetHeader (unix timestamp). Returns zero if no recognizable header
|
||||
// is present.
|
||||
func parseRetryAfter(h http.Header, resetHeader string, clk quartz.Clock) time.Duration {
|
||||
if clk == nil {
|
||||
clk = quartz.NewReal()
|
||||
}
|
||||
// Retry-After header: seconds until retry.
|
||||
if ra := h.Get("Retry-After"); ra != "" {
|
||||
if secs, err := strconv.Atoi(ra); err == nil {
|
||||
return time.Duration(secs) * time.Second
|
||||
}
|
||||
}
|
||||
// Reset header: unix timestamp. We compute the duration from now
|
||||
// according to the caller's clock.
|
||||
if reset := h.Get(resetHeader); reset != "" {
|
||||
if ts, err := strconv.ParseInt(reset, 10, 64); err == nil {
|
||||
return time.Unix(ts, 0).Sub(clk.Now())
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// checkRateLimitError returns a *RateLimitError when resp indicates a
|
||||
// rate limit (HTTP 403 or 429) with recognizable retry headers;
|
||||
// otherwise nil. A nil resp returns nil.
|
||||
func checkRateLimitError(resp *http.Response, clk quartz.Clock, resetHeader string) error {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
if resp.StatusCode != http.StatusForbidden && resp.StatusCode != http.StatusTooManyRequests {
|
||||
return nil
|
||||
}
|
||||
if clk == nil {
|
||||
clk = quartz.NewReal()
|
||||
}
|
||||
retryAfter := parseRetryAfter(resp.Header, resetHeader, clk)
|
||||
if retryAfter <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &RateLimitError{RetryAfter: clk.Now().Add(retryAfter + RateLimitPadding)}
|
||||
}
|
||||
|
||||
// countDiffLines counts added and deleted lines in a unified diff. It excludes
|
||||
// file header lines such as +++ b/file and --- a/file.
|
||||
func countDiffLines(diff string) (additions, deletions int32) {
|
||||
for _, line := range strings.Split(diff, "\n") {
|
||||
if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") {
|
||||
additions++
|
||||
} else if strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---") {
|
||||
deletions++
|
||||
}
|
||||
}
|
||||
return additions, deletions
|
||||
}
|
||||
|
||||
// RateLimitError indicates the git provider's API rate limit was hit.
|
||||
|
||||
Reference in New Issue
Block a user