Documentation
¶
Overview ¶
Package githubingest reads individual file contents from a GitHub repository via the GitHub Contents API, authenticated as a GitHub App installation.
This package is optional and separately packaged from pkg/semantics: pkg/githubingest depends on github.com/google/go-github and github.com/bradleyfalzon/ghinstallation/v2, and pkg/semantics never imports pkg/githubingest or these dependencies. Consumers that only analyze raw source bytes never need to build or vendor a GitHub client.
ReadFile issues two Contents API requests per call: the direct file fetch, plus a listing of the file's parent directory so it can detect an in-repo symlink that GitHub's Contents API would otherwise resolve transparently and report as a plain file (see reader.go's rejectIfPathIsSymlink). The second request lists only the immediate parent directory, not a whole-repository tree, so its cost does not scale with repository size.
That directory listing is itself capped at GitHub's documented limit of 1,000 entries per directory: in a parent directory larger than that, a symlink entry can be absent from the (truncated) listing and pass the check undetected, unlike the Git Trees API, which at least reports truncation via a Truncated flag -- the Contents API listing gives no such signal. This is a narrower version of the same class of blind spot as a whole-repository tree walk would have (per-directory, 1,000+ files, rather than whole-repo size), and considered acceptable for v1.
Index ¶
- Constants
- Variables
- type CredentialResolver
- type CredentialResolverConfig
- type FileFilter
- type FileMetadata
- type GitHubAppConfig
- type GitHubFileReader
- func (r *GitHubFileReader) ListFiles(ctx context.Context, ref GitHubTreeRef, opts TreeListOptions) ([]TreeEntry, error)
- func (r *GitHubFileReader) ReadFile(ctx context.Context, ref GitHubFileRef) ([]byte, FileMetadata, error)
- func (r *GitHubFileReader) ResolveCommitSHA(ctx context.Context, owner, repo, ref string) (string, error)
- type GitHubFileRef
- type GitHubTreeRef
- type TreeEntry
- type TreeListOptions
Examples ¶
Constants ¶
const DefaultCredentialResolverHTTPTimeout = 10 * time.Second
DefaultCredentialResolverHTTPTimeout bounds outbound App-JWT-authenticated HTTP calls (installation resolution, token minting) issued by a CredentialResolver.
const DefaultGitHubFileReaderHTTPTimeout = 10 * time.Second
DefaultGitHubFileReaderHTTPTimeout bounds outbound Contents API HTTP calls issued by GitHubFileReader (ReadFile and parent-directory listings). Matches DefaultCredentialResolverHTTPTimeout so App-authenticated paths share one production hang bound.
Variables ¶
var ( // ErrAuth indicates the GitHub API rejected the request as unauthorized // or forbidden (HTTP 401/403). ErrAuth = errors.New("githubingest: authentication failed") // ErrNotFound indicates the requested path does not exist at the given // ref (HTTP 404). ErrNotFound = errors.New("githubingest: file not found") // ErrUnsupportedContent indicates the requested path resolved to // something other than a regular file (a directory, symlink, or // submodule). ErrUnsupportedContent = errors.New("githubingest: unsupported content type") // ErrEmptyContent indicates the file exists but decodes to zero bytes. ErrEmptyContent = errors.New("githubingest: file content is empty") // ErrTooLarge indicates the file exceeds the GitHub Contents API's // 1 MiB size limit. ErrTooLarge = errors.New("githubingest: file exceeds maximum supported size") )
Sentinel errors returned by GitHubFileReader. Callers should use errors.Is to test for these.
Functions ¶
This section is empty.
Types ¶
type CredentialResolver ¶ added in v0.2.0
type CredentialResolver struct {
// contains filtered or unexported fields
}
CredentialResolver mints GitHub App installation tokens: the single seam ADR-002 rule 5 requires, shared by GitHubFileReader-style fixed-installation reads and internal/authz's dynamic owner/repo -> installation resolution. It never appears without a valid AppID/PrivateKey; it holds no long-lived installation token itself (a fresh mint per InstallationToken call, no caching -- matches ADR-003's "no caching in v1").
func NewCredentialResolver ¶ added in v0.2.0
func NewCredentialResolver(cfg CredentialResolverConfig) (*CredentialResolver, error)
NewCredentialResolver builds a CredentialResolver authenticated as the GitHub App described by cfg.
func (*CredentialResolver) InstallationToken ¶ added in v0.2.0
func (c *CredentialResolver) InstallationToken(ctx context.Context, installationID int64) (string, error)
InstallationToken mints a fresh installation access token scoped to installationID via POST /app/installations/{id}/access_tokens. Maps errors the same way as ResolveInstallationID.
func (*CredentialResolver) ResolveInstallationID ¶ added in v0.2.0
func (c *CredentialResolver) ResolveInstallationID(ctx context.Context, owner, repo string) (int64, error)
ResolveInstallationID resolves the installation governing owner/repo via GET /repos/{owner}/{repo}/installation (App JWT auth). Returns ErrNotFound on GitHub 404 (deliberately covers both "no such repo" and "App not installed there" -- ADR-003) and ErrAuth on 401/403. Any other failure is returned wrapped, unmatched to a sentinel (caller treats as transient/503).
type CredentialResolverConfig ¶ added in v0.2.0
type CredentialResolverConfig struct {
AppID int64
PrivateKey []byte // PEM (PKCS#1), never logged
BaseURL string // optional; GitHub Enterprise
Transport http.RoundTripper // optional; base transport (tests)
}
CredentialResolverConfig is GitHub App authentication for a CredentialResolver.
type FileFilter ¶ added in v0.2.0
FileFilter reports whether path should be included in a ListFiles result. pkg/githubingest never imports pkg/semantics, so callers that want to restrict a listing to specific source languages (e.g. ".go", ".ts", ".tsx") must supply their own filter rather than relying on any built-in language list.
type FileMetadata ¶
type FileMetadata struct {
Path string `json:"path"`
Ref string `json:"ref"`
SHA string `json:"sha"`
Size int `json:"size"`
}
FileMetadata describes a file read via GitHubFileReader.ReadFile.
type GitHubAppConfig ¶
type GitHubAppConfig struct {
AppID int64
InstallationID int64
PrivateKey []byte // PEM (PKCS#1) as issued by GitHub; never logged
BaseURL string // optional; GitHub Enterprise
Transport http.RoundTripper // optional; base transport (tests, future rate limiting)
}
GitHubAppConfig is GitHub App authentication for GitHubFileReader.
type GitHubFileReader ¶
type GitHubFileReader struct {
// contains filtered or unexported fields
}
GitHubFileReader reads file contents from GitHub repositories, authenticated as a GitHub App installation.
func NewGitHubFileReader ¶
func NewGitHubFileReader(cfg GitHubAppConfig) (*GitHubFileReader, error)
NewGitHubFileReader builds a GitHubFileReader authenticated as the GitHub App installation described by cfg.
Example ¶
ExampleNewGitHubFileReader shows constructing a GitHubFileReader from a GitHub App's credentials. Construction never touches the network - authentication happens lazily on the first ReadFile call. The private key below is generated on the spot purely so this example runs offline and deterministically; a real caller would load their GitHub App's issued PEM key instead.
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/lousy-agents/coach/pkg/githubingest"
)
func main() {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Println(err)
return
}
pemKey := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
reader, err := githubingest.NewGitHubFileReader(githubingest.GitHubAppConfig{
AppID: 123,
InstallationID: 456,
PrivateKey: pemKey,
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(reader != nil)
}
Output: true
func NewGitHubFileReaderFromToken ¶ added in v0.2.0
func NewGitHubFileReaderFromToken(token, baseURL string) (*GitHubFileReader, error)
NewGitHubFileReaderFromToken builds a GitHubFileReader authenticated with a pre-minted installation access token (from CredentialResolver.InstallationToken). baseURL is optional (GitHub Enterprise). Prefer this over NewGitHubFileReader when the caller already holds a token via the ADR-002 CredentialResolver seam.
func (*GitHubFileReader) ListFiles ¶ added in v0.2.0
func (r *GitHubFileReader) ListFiles(ctx context.Context, ref GitHubTreeRef, opts TreeListOptions) ([]TreeEntry, error)
ListFiles recursively walks ref's repository tree via the Contents API, returning every regular file matching opts.Filter. An empty repository or an empty matching set returns an empty, non-nil slice and a nil error.
The walk skips symlink and submodule entries entirely rather than following them, mirroring rejectIfPathIsSymlink's concern in reader.go: a symlink or submodule could otherwise be used to escape the repository boundary or double-count content.
func (*GitHubFileReader) ReadFile ¶
func (r *GitHubFileReader) ReadFile(ctx context.Context, ref GitHubFileRef) ([]byte, FileMetadata, error)
ReadFile fetches the raw bytes and metadata of a single file at ref.
func (*GitHubFileReader) ResolveCommitSHA ¶ added in v0.2.0
func (r *GitHubFileReader) ResolveCommitSHA(ctx context.Context, owner, repo, ref string) (string, error)
ResolveCommitSHA resolves ref to a commit object SHA for owner/repo. Empty ref resolves the repository default branch tip (not the literal "HEAD"). Branch names, tags, and already-resolved SHAs are accepted via the Commits API.
type GitHubFileRef ¶
type GitHubFileRef struct{ Owner, Repo, Ref, Path string }
GitHubFileRef identifies a single file within a repository at a ref.
type GitHubTreeRef ¶ added in v0.2.0
type GitHubTreeRef struct{ Owner, Repo, Ref string }
GitHubTreeRef identifies a repository at a ref whose file tree should be listed, as opposed to GitHubFileRef's single path.
type TreeEntry ¶ added in v0.2.0
TreeEntry describes one matching file discovered by GitHubFileReader.ListFiles.
type TreeListOptions ¶ added in v0.2.0
type TreeListOptions struct {
// Filter, if non-nil, restricts the result to paths for which it
// returns true. A nil Filter matches every regular file.
Filter FileFilter
// MaxFiles caps the number of matching files collected before the walk
// aborts with an error wrapping ErrTooLarge. Zero means unlimited.
MaxFiles int
// MaxTotalBytes caps the sum of matching files' reported sizes before
// the walk aborts with an error wrapping ErrTooLarge. Zero means
// unlimited. Sizes come from the Contents API's directory-listing
// "size" field, so this budget is checked without fetching any file's
// content.
MaxTotalBytes int64
}
TreeListOptions is GitHubFileReader.ListFiles's tree-walk budget and filter.