github

package
v0.26.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNoTokens = errors.New("no available GitHub tokens")

ErrNoTokens reports that every token in the pool is exhausted or the pool is empty. Callers pacing background work use it to back off until the pool recovers rather than retrying into a guaranteed failure.

Functions

func AggregateGraphQLQuota

func AggregateGraphQLQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)

AggregateGraphQLQuota mirrors AggregateQuota for the GraphQL rate-limit family. Used by enrichment paths that fetch GHSA security credits.

func AggregateQuota

func AggregateQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)

AggregateQuota returns the aggregate remaining percentage and earliest reset time across all tokens, for the core (REST) rate-limit family. Returns 100 if quotas is empty or all errored.

func AggregateSearchQuota

func AggregateSearchQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)

AggregateSearchQuota mirrors AggregateQuota for the search-API rate-limit family. The scoring path issues three search calls per contributor (merged/closed/recent PRs) so the search quota is exhausted long before the core quota under load.

func StartPoolRefresh

func StartPoolRefresh(ctx context.Context, pool *TokenPool, refreshFn RefreshFunc,
	notifyCh <-chan struct{}, tickInterval time.Duration, onDropped DroppedTokenObserver) func()

StartPoolRefresh runs a background goroutine that refreshes the token pool when tokens are near expiry or when signaled via notifyCh. Pass tickInterval=0 to use the default (1 minute). Returns a stop function that cancels the goroutine and waits for it to exit.

Types

type ArchiveHints

type ArchiveHints struct {
	PRsMerged         int64
	PRsClosed         int64
	RecentPRRepoCount int64
	Trusted           bool
}

ArchiveHints provides pre-computed signals from GH Archive data, allowing fetchSignals to skip redundant GitHub Search API calls. When nil, all signals are fetched from the GitHub API. When Trusted is true, hints replace API calls. Otherwise the Search API is attempted first and hints serve as a fallback when API calls fail.

type Client

type Client interface {
	FetchUser(ctx context.Context, username string) (*UserProfile, error)
	FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
	IsOrgMember(ctx context.Context, org, username string) (bool, error)
	// ListUserRepos returns up to maxRepos owned repositories. Caller is
	// responsible for any forks/archived filtering. The order is determined
	// by the GitHub API (currently "pushed" descending). maxRepos is
	// clamped to a sane upper bound by the implementation.
	ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
	// FetchSecurityCredits returns the contributor's GHSA security
	// advisory credits — published advisories where the user is credited
	// as reporter, fixer, analyst, or other role. Uses the GitHub
	// GraphQL API since the REST surface does not expose the per-user
	// credits connection. Empty slice is a valid result (user has no
	// public credits); error indicates fetch failure.
	FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
}

Client abstracts GitHub API access.

type DroppedTokenObserver

type DroppedTokenObserver func(tokens []string)

DroppedTokenObserver is called with the set of tokens dropped from the pool on each refresh, so per-token caches (e.g., PoolClient's *gh.Client memoization) can be invalidated. Optional — nil is safe.

type InstallationClient

type InstallationClient struct {
	// contains filtered or unexported fields
}

InstallationClient implements Client using GitHub App installation tokens. Tokens are cached and auto-refreshed before expiry. Mint operations are serialized through a separate mint mutex so concurrent requesters block at most one minter — the data mutex (RWMutex) stays free for readers.

func NewInstallationClient

func NewInstallationClient(cfg *tenant.GitHubAppConfig, installationID int64) *InstallationClient

NewInstallationClient returns a Client backed by auto-refreshing installation tokens.

func (*InstallationClient) FetchSecurityCredits

func (c *InstallationClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)

FetchSecurityCredits queries the contributor's GHSA credits via GraphQL.

func (*InstallationClient) FetchSignals

func (c *InstallationClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)

FetchSignals retrieves scoring signals for the given user, optionally scoped to a repo.

func (*InstallationClient) FetchUser

func (c *InstallationClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)

FetchUser retrieves a GitHub user profile.

func (*InstallationClient) IsOrgMember

func (c *InstallationClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)

func (*InstallationClient) ListUserRepos

func (c *InstallationClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)

ListUserRepos retrieves the contributor's owned repositories.

type InvalidationEvent

type InvalidationEvent struct {
	At             time.Time
	Label          string
	InstallationID int64
	Permanent      bool
}

InvalidationEvent records that a token was 401'd and removed from rotation. Exposed via RecentInvalidations for the admin dashboard.

type PATClient

type PATClient struct {
	// contains filtered or unexported fields
}

PATClient implements Client using a personal access token.

func NewPATClient

func NewPATClient(_ context.Context, token string) *PATClient

NewPATClient returns a Client backed by a personal access token. The ctx parameter is accepted for API consistency but is not stored; context.Background() is used for the oauth2 HTTP client because the returned PATClient outlives any single request context.

func (*PATClient) FetchSecurityCredits

func (c *PATClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)

FetchSecurityCredits queries the contributor's GHSA credits via GraphQL.

func (*PATClient) FetchSignals

func (c *PATClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)

FetchSignals retrieves scoring signals for the given user, optionally scoped to a repo.

func (*PATClient) FetchUser

func (c *PATClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)

FetchUser retrieves a GitHub user profile.

func (*PATClient) IsOrgMember

func (c *PATClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)

IsOrgMember checks if the user is a member of the given org.

func (*PATClient) ListUserRepos

func (c *PATClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)

ListUserRepos retrieves the contributor's owned repositories.

type PoolClient

type PoolClient struct {
	// contains filtered or unexported fields
}

PoolClient implements Client using a TokenPool for round-robin token rotation with bounded retry on rate-limit errors. Each token rotation honors the actual reset time GitHub returns; the retry is backed by a generic helper so all five Client methods share the same loop.

func NewPoolClient

func NewPoolClient(pool *TokenPool) *PoolClient

NewPoolClient returns a Client backed by the given token pool.

func (*PoolClient) FetchSecurityCredits

func (c *PoolClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)

FetchSecurityCredits queries the contributor's GHSA credits via GraphQL, with automatic token rotation on rate limit.

func (*PoolClient) FetchSignals

func (c *PoolClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)

FetchSignals retrieves scoring signals with automatic token rotation on rate limit.

func (*PoolClient) FetchUser

func (c *PoolClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)

FetchUser retrieves a GitHub user profile.

func (*PoolClient) InvalidateTokens

func (c *PoolClient) InvalidateTokens(tokens []string)

InvalidateTokens drops cached *gh.Client entries for the given tokens. Called when the pool replaces entries (e.g., installation tokens re-minted) so we don't leak references to no-longer-active tokens. Safe to call with tokens that were never cached.

func (*PoolClient) IsOrgMember

func (c *PoolClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)

IsOrgMember checks if the user is a member of the given org with token rotation.

func (*PoolClient) ListUserRepos

func (c *PoolClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)

ListUserRepos retrieves the contributor's owned repositories with automatic token rotation on rate limit.

func (*PoolClient) Pool

func (c *PoolClient) Pool() *TokenPool

Pool returns the underlying token pool for operational visibility.

type PoolEntry

type PoolEntry struct {
	InstallationID int64
	Label          string // target login (org/user) or "PAT"
	Token          string
	ExpiresAt      time.Time // zero means never expires (PAT)
}

PoolEntry describes a token source for the pool.

type RefreshFunc

type RefreshFunc func(ctx context.Context) ([]PoolEntry, error)

RefreshFunc loads current installations and returns pool entries.

type Repo

type Repo struct {
	Name        string
	FullName    string
	Description string
	Language    string
	Stars       int
	Fork        bool
	Archived    bool
	PushedAt    time.Time
}

Repo holds a subset of GitHub repository metadata used by enrichment aggregation. Only fields actually consumed by the aggregator are included; extending this struct is cheap.

type SecurityAdvisoryCredit

type SecurityAdvisoryCredit struct {
	AdvisoryID  string    // GHSA identifier, e.g. "GHSA-xxxx-yyyy-zzzz"
	CreditType  string    // reporter / fixer / analyst / remediation_developer / etc.
	Severity    string    // critical / high / moderate / low / unknown (lowercased)
	CVEID       string    // CVE identifier when assigned, empty otherwise
	Summary     string    // short advisory summary
	PublishedAt time.Time // advisory publication date
}

SecurityAdvisoryCredit represents one GHSA credit edge: a contributor is credited as `CreditType` on advisory `AdvisoryID`. Fields mirror the subset of the GraphQL `SecurityAdvisoryCredit` and embedded `SecurityAdvisory` types that we surface in enrichment.

type TokenPool

type TokenPool struct {
	// contains filtered or unexported fields
}

TokenPool manages a pool of GitHub API tokens using round-robin selection. Thread-safe. Supports marking tokens as exhausted after rate limit errors; each exhaustion records the actual reset time GitHub returned (or a fallback) so the token only re-enters rotation when it is genuinely usable. Adapted from DevPulse pkg/data/ghutil/tokenpool.go.

func NewTokenPool

func NewTokenPool(tokens ...string) *TokenPool

NewTokenPool creates a pool from one or more tokens. Tokens can be passed individually or as a single comma-separated string.

func NewTokenPoolFromEntries

func NewTokenPoolFromEntries(entries []PoolEntry) *TokenPool

NewTokenPoolFromEntries creates a pool from typed entries with metadata.

func (*TokenPool) ActiveCount

func (p *TokenPool) ActiveCount() int

ActiveCount returns the number of tokens that are usable (not exhausted and not expired).

func (*TokenPool) CheckQuotas

func (p *TokenPool) CheckQuotas(ctx context.Context) []TokenQuota

CheckQuotas calls the GitHub rate_limit API for each token in the pool. This endpoint is free (does not count against quota).

func (*TokenPool) EarliestReset

func (p *TokenPool) EarliestReset() (earliest time.Time)

EarliestReset returns the soonest time an exhausted token re-enters rotation, or the zero time when nothing is currently exhausted. Pairs with ActiveCount so background work can sleep exactly until capacity returns instead of retrying into a dry pool.

func (*TokenPool) Exhaust

func (p *TokenPool) Exhaust(token string, resetAt time.Time, family string)

Exhaust marks the given token as exhausted so Token() skips it until resetAt. A zero resetAt falls back to tokenResetFallback from now; a resetAt in the past is treated as the fallback (defensive against clock skew between this host and GitHub). The family parameter records which rate-limit family triggered the exhaustion (core/search/graphql/abuse/ unknown) so log-based metrics and alerts can distinguish routine search rotation from real REST exhaustion.

func (*TokenPool) InvalidateAuth

func (p *TokenPool) InvalidateAuth(token string)

InvalidateAuth marks the given token as exhausted in response to a 401 Bad credentials response from GitHub. Distinct log/path from Exhaust so operators can distinguish revoked/rotated credentials from rate-limit backoff.

Installation tokens (installationID != 0) get tokenResetFallback so they re-enter rotation after roughly the window an installation token would naturally expire — by then the refresh goroutine will have re-minted them. PAT entries (installationID == 0) get a far-future until so they are permanently disabled until the process restarts: a PAT cannot be re-minted and retrying a revoked one only burns latency.

func (*TokenPool) Labels

func (p *TokenPool) Labels() []string

Labels returns the label for each entry in pool order.

func (*TokenPool) NeedsRefresh

func (p *TokenPool) NeedsRefresh() bool

NeedsRefresh returns true if any installation token is within the expiry buffer.

func (*TokenPool) RecentInvalidations

func (p *TokenPool) RecentInvalidations(since time.Time) []InvalidationEvent

RecentInvalidations returns events newer than since, oldest first. Pass a zero time to receive every event still in the ring. Safe for the admin dashboard to call without coordinating with the refresh loop.

func (*TokenPool) Replace

func (p *TokenPool) Replace(entries []PoolEntry) []string

Replace atomically swaps the pool entries. Resets cursor, counts, and exhaustion state. Returns the set of token strings that were dropped so callers can invalidate any per-token caches (e.g., PoolClient's *gh.Client memoization). The returned slice contains only tokens that no longer appear in the new entries — tokens that survived the swap stay valid.

func (*TokenPool) SetRefreshCh

func (p *TokenPool) SetRefreshCh(ch chan<- struct{})

SetRefreshCh sets a channel that Token() will signal (non-blocking) when it selects a near-expiry token, allowing the refresh goroutine to re-mint early.

func (*TokenPool) SignalRefresh

func (p *TokenPool) SignalRefresh()

SignalRefresh nudges the refresh goroutine to re-mint installation tokens, if a refresh channel has been registered. Non-blocking: if the channel already has a pending signal, this is a no-op. Used by callers that detect a poisoned token (e.g., auth failure) and want fresh credentials minted ahead of the periodic tick.

func (*TokenPool) Size

func (p *TokenPool) Size() int

Size returns the total number of tokens in the pool (including exhausted).

func (*TokenPool) Token

func (p *TokenPool) Token() string

Token returns the next non-exhausted token in the round-robin rotation. Returns "" when all tokens are exhausted or the pool is empty. Entries with a non-zero ExpiresAt that falls within tokenExpiryBuffer are skipped.

func (*TokenPool) UsageCounts

func (p *TokenPool) UsageCounts() []int

UsageCounts returns a copy of per-token call counts (indexed by pool position).

type TokenQuota

type TokenQuota struct {
	Index            int
	Label            string
	InstallationID   int64
	Limit            int
	Remaining        int
	Reset            time.Time
	SearchLimit      int
	SearchRemaining  int
	SearchReset      time.Time
	GraphQLLimit     int
	GraphQLRemaining int
	GraphQLReset     time.Time
	Error            string
}

TokenQuota holds rate limit info for a single GitHub API token. Limit / Remaining / Reset describe the core (REST) family; the Search* and GraphQL* fields cover the secondary families that the scoring path actually burns through (3 search calls per signal fetch, GraphQL for security credits). Empty values for a family mean GitHub didn't return it (rare; older endpoints).

type UserProfile

type UserProfile struct {
	Username    string
	Name        string
	Email       string
	AvatarURL   string
	Company     string
	Location    string
	Bio         string
	Website     string
	CreatedAt   time.Time
	Suspended   bool
	Followers   int64
	Following   int64
	PublicRepos int64
}

UserProfile holds GitHub user metadata.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL