Documentation
¶
Overview ¶
Package github implements the VCS release provider and API client for GitHub repositories, supporting both public and token-authenticated access. It provides repository management, pull requests, release listing, asset downloads, and file retrieval through a testable [GitHubClient] interface. Client construction uses package-owned [ClientSettings]; GTB config integration lives in [ClientSettingsFromConfig].
Index ¶
- Constants
- func NewReleaseProvider(ctx context.Context, settings Settings) (forge.Provider, error)
- type GitHubReleaseProvider
- func (p *GitHubReleaseProvider) CreateIssue(ctx context.Context, owner, repo string, draft forge.IssueDraft) (forge.Issue, error)
- func (p *GitHubReleaseProvider) DownloadReleaseAsset(ctx context.Context, owner, repo string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
- func (p *GitHubReleaseProvider) GetFile(ctx context.Context, owner, repo, path, ref string, maxBytes int64) ([]byte, error)
- func (p *GitHubReleaseProvider) GetIssue(ctx context.Context, owner, repo string, number int) (forge.Issue, error)
- func (p *GitHubReleaseProvider) GetLatestRelease(ctx context.Context, owner, repo string) (forge.Release, error)
- func (p *GitHubReleaseProvider) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (forge.Release, error)
- func (p *GitHubReleaseProvider) GetSite(ctx context.Context, owner, repo string) (forge.Site, error)
- func (p *GitHubReleaseProvider) ListComments(ctx context.Context, owner, repo string, number int, q forge.CommentQuery, ...) error
- func (p *GitHubReleaseProvider) ListReleases(ctx context.Context, owner, repo string, limit int) ([]forge.Release, error)
- func (p *GitHubReleaseProvider) ListRepositories(ctx context.Context, namespace string, opts forge.RepositoryListOptions, ...) error
- func (p *GitHubReleaseProvider) Login(ctx context.Context, prompter forge.Prompter) (string, error)
- func (p *GitHubReleaseProvider) SearchIssues(ctx context.Context, owner, repo string, q forge.IssueQuery, ...) error
- func (p *GitHubReleaseProvider) UploadKey(ctx context.Context, name string, publicKey []byte) error
- type Settings
Constants ¶
const DefaultClientIDEnv = "GITHUB_CLIENT_ID"
DefaultClientIDEnv is the well-known environment variable consulted for the OAuth app client ID that the interactive device-flow login ([Authenticator]) requires, when Settings.ClientID is empty.
const DefaultTokenEnv = "GITHUB_TOKEN"
DefaultTokenEnv is the well-known environment variable the default credential composition consults last. See SettingsFromConfig.
This is the one rung of the old resolution chain that layer composition cannot express — an unprefixed, forge-chosen name — and it is what CI injects, so it survives as a composed default rather than a hardcoded tier.
The suppression below is a false positive that cannot be designed away: gosec G101 matches the literal "GITHUB_TOKEN" against its list of known credential patterns, but this is the NAME of an environment variable, not a secret — and it is the name GitHub's own tooling uses, so it cannot be spelled differently. Renaming the constant does not help; gosec keys on the value. The sibling providers escape only because "GITEA_TOKEN" and "DIRECT_TOKEN" are not on that list.
Variables ¶
This section is empty.
Functions ¶
func NewReleaseProvider ¶
NewReleaseProvider builds a GitHub release provider from explicit typed settings, constructing its own API client.
The credential comes from Settings.Credential, or — when that is nil — from DefaultTokenEnv. The context bounds its resolution: a source the caller supplied may reach a keychain or a remote secret store.
Types ¶
type GitHubReleaseProvider ¶
type GitHubReleaseProvider struct {
// contains filtered or unexported fields
}
GitHubReleaseProvider implements forge.Provider.
func (*GitHubReleaseProvider) CreateIssue ¶ added in v0.4.0
func (p *GitHubReleaseProvider) CreateIssue( ctx context.Context, owner, repo string, draft forge.IssueDraft, ) (forge.Issue, error)
CreateIssue files an issue.
Labels are names here, so unlike the Gitea adapter there is nothing to resolve.
func (*GitHubReleaseProvider) DownloadReleaseAsset ¶
func (p *GitHubReleaseProvider) DownloadReleaseAsset(ctx context.Context, owner, repo string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
func (*GitHubReleaseProvider) GetFile ¶ added in v0.3.0
func (p *GitHubReleaseProvider) GetFile( ctx context.Context, owner, repo, path, ref string, maxBytes int64, ) ([]byte, error)
GetFile reads one file at a ref without cloning.
GitHub is the one provider that can enforce maxBytes PROPERLY: DownloadContents hands back an io.ReadCloser, so the bound is applied to the stream and a hostile or merely enormous file is cut off mid-flight. GitLab and Gitea have to pre-check a reported size instead, because their SDKs buffer the whole body before returning it — a weaker guarantee the contract permits but does not prefer.
The limit is read with one extra byte of headroom so exceeding it is detectable rather than silently truncating at exactly maxBytes.
func (*GitHubReleaseProvider) GetIssue ¶ added in v0.4.0
func (p *GitHubReleaseProvider) GetIssue( ctx context.Context, owner, repo string, number int, ) (forge.Issue, error)
GetIssue returns one issue by its per-repository number.
func (*GitHubReleaseProvider) GetLatestRelease ¶
func (*GitHubReleaseProvider) GetReleaseByTag ¶
func (*GitHubReleaseProvider) GetSite ¶ added in v0.3.0
func (p *GitHubReleaseProvider) GetSite( ctx context.Context, owner, repo string, ) (forge.Site, error)
GetSite reports a repository's GitHub Pages site.
Reading Pages settings needs the repo scope, so a refusal is routine for a read-only caller — and it is deliberately NOT folded into ErrNotFound. Telling a caller "this repository has no site" when the truth is "your token could not ask" is how a qualifying repository silently drops out of a corpus.
func (*GitHubReleaseProvider) ListComments ¶ added in v0.4.0
func (p *GitHubReleaseProvider) ListComments( ctx context.Context, owner, repo string, number int, q forge.CommentQuery, yield func(forge.Comment) bool, ) error
ListComments yields an issue's comments.
GitHub takes Since server-side, so there is no emulation here — and it returns oldest-first, the opposite of the GitLab adapter's emulated newest-first. The contract promises no ordering precisely because those two disagree.
func (*GitHubReleaseProvider) ListReleases ¶
func (p *GitHubReleaseProvider) ListReleases(ctx context.Context, owner, repo string, limit int) ([]forge.Release, error)
ListReleases returns up to limit releases, paginating across GitHub's pages until the limit is met or history is exhausted. A limit <= 0 means "no explicit bound" — the natural first page. See forge.Provider.
func (*GitHubReleaseProvider) ListRepositories ¶ added in v0.3.0
func (p *GitHubReleaseProvider) ListRepositories( ctx context.Context, namespace string, opts forge.RepositoryListOptions, yield func(forge.Repository) bool, ) error
ListRepositories enumerates the repositories owned by a namespace.
GitHub reads organisations and users through different endpoints and a login does not say which it is, so the kind is RESOLVED before enumeration begins: GET /users/{name} reports "User" or "Organization" authoritatively, in one request.
The shortcut this deliberately avoids — call the org endpoint, fall back to the user endpoint on 404 — fails OPEN. GitHub answers 404 rather than 403 for an organisation the token cannot see, so as not to leak its existence, and /users/{login}/repos then SUCCEEDS for that same organisation returning its PUBLIC repositories only. The caller would receive a short list indistinguishable from a complete one, which for a corpus defined by a predicate is silent truncation rather than an error.
func (*GitHubReleaseProvider) Login ¶ added in v0.2.0
Login implements the optional forge.Authenticator capability via GitHub's OAuth device flow (RFC 8628): it requests a device code, surfaces it through the forge.Prompter for the user to enter in a browser, then polls for the access token. Presentation — including whether to open a browser at the verification URL — belongs to the Prompter; this adapter speaks only the protocol.
It returns an error wrapping forge.ErrNotSupported when no OAuth client ID is configured (Settings.ClientID or DefaultClientIDEnv), so the caller falls back to manual token entry.
func (*GitHubReleaseProvider) SearchIssues ¶ added in v0.4.0
func (p *GitHubReleaseProvider) SearchIssues( ctx context.Context, owner, repo string, q forge.IssueQuery, yield func(forge.Issue) bool, ) error
SearchIssues yields issues matching q.
The endpoint is chosen by whether q.Text is set, because only one of the two can honour it. Both paths filter out pull requests: GitHub returns them from the issue endpoints, and a caller looking for a duplicate support question must not be handed a pull request and told it already asked.
func (*GitHubReleaseProvider) UploadKey ¶ added in v0.2.0
UploadKey implements the optional forge.KeyManager capability: it registers an OpenSSH-format public key on the authenticated account via GitHub's user-keys API. The provider's resolved token (see [Settings.Auth]) authorises the call; name is the label shown in the account's key list.
type Settings ¶
type Settings struct {
ReleaseSource forge.ReleaseSourceConfig
// APIURL overrides the API endpoint. Empty derives it from
// ReleaseSource.Host, or uses github.com.
APIURL string `json:"api_url" yaml:"api_url"`
// UploadURL overrides the asset-upload endpoint. Empty derives it from
// APIURL.
UploadURL string `json:"upload_url" yaml:"upload_url"`
// Credential yields the token this provider authenticates with.
//
// Nil is not an error: the client falls back to [DefaultTokenEnv], so
// construction from configuration alone keeps working and a public
// repository needs nothing at all. Set it to take over entirely —
// including to hand in a token directly:
//
// Credential: forge.StaticCredential(token)
Credential forge.CredentialSource
// Logger receives this provider's diagnostics. Nil discards them, and is
// never [slog.Default]. See [forge.WithLogger] for the registry route.
Logger *slog.Logger
// ClientID is the OAuth app client ID used by the interactive device-flow
// login ([Authenticator]). Empty falls back to [DefaultClientIDEnv]; when
// neither is set, Login reports [forge.ErrNotSupported] and the caller
// prompts for a token manually.
ClientID string `json:"client_id" yaml:"client_id"`
// Scopes overrides the OAuth scopes requested at login. Empty uses a
// sensible default (repo, read:org, gist).
Scopes []string `json:"scopes" yaml:"scopes"`
}
Settings contains the typed configuration needed to construct a GitHub release provider, without binding it to any config container.
The shape matches every other provider: a release source, a forge.CredentialSource, and the endpoint overrides this forge needs.
func SettingsFromConfig ¶
func SettingsFromConfig(src forge.ReleaseSourceConfig, cfg forge.Config) Settings
SettingsFromConfig adapts the github config subtree into typed provider settings. It preserves the existing `url.*` and `auth.client_id` keys.
The credential composition IS the precedence, and it is written here rather than hidden in a resolution chain: the configured key, then the well-known variable. A caller wanting a different order — or a different key entirely — builds their own forge.CredentialSource and assigns Settings.Credential.
A nil cfg is not special-cased: forge.ConfigCredential treats a nil config as contributing nothing, so a config-free public lookup still reaches the environment fallback.