Documentation
¶
Overview ¶
Package github reads a repository's GitHub releases for programs that install from release assets. Lookups are cached on disk, because the API allows 60 calls an hour per IP address without a token and 5,000 with one, and a stale answer stands in when GitHub cannot be reached or the limit is reached. The package also picks the release a version names, narrows its assets to the ones a user of this OS would install, recognises the same download across releases, lists a remote zip's files without downloading it, and can lend requests the user's gh login.
Index ¶
- Constants
- Variables
- func GHCLI(ctx context.Context) ([]byte, error)
- func IsGitHubHost(host string) bool
- func Key(owner, repo, tag, asset string) string
- func Observe(resp *http.Response) error
- func RateLimited(resp *http.Response) error
- func Select(all []Release, version string, prerelease bool, keep func(Asset) bool) (Release, []Asset, error)
- func Shape(name string) string
- func ValidRepo(ownerRepo string) bool
- func ZipNames(ctx context.Context, hc *http.Client, a Asset) ([]string, error)
- type Asset
- type Auth
- type Client
- func (c *Client) CachedReleases(owner, repo string) (releases []Release, all, ok bool)
- func (c *Client) ReleaseNotes(ctx context.Context, owner, repo, tag string) (string, error)
- func (c *Client) Releases(ctx context.Context, owner, repo string) ([]Release, error)
- func (c *Client) ReleasesBetween(ctx context.Context, owner, repo, installed, latest string) ([]Release, error)
- type Rate
- type Release
Constants ¶
const MaxChangelog = 10
MaxChangelog bounds how many releases ReleasesBetween returns.
Variables ¶
var ( ErrNoRelease = errors.New("no matching release") ErrNoAsset = errors.New("the release has no matching asset") )
ErrNoRelease and ErrNoAsset say why Select found nothing to install.
var DefaultAuth = &Auth{}
DefaultAuth authorizes the requests FetchReleases and Client make. It sends nothing until Run is set, because a library should not start a program on its first request unasked.
var ErrInvalidRepo = errors.New("not a GitHub owner/repo")
ErrInvalidRepo is returned for an owner/repo that is not a GitHub name.
Functions ¶
func IsGitHubHost ¶
IsGitHubHost reports whether host is one that a token may be sent to.
func Key ¶
Key is a stable, file-name-safe id of a release asset: github-<owner>-<repo>-<tag>-<asset>, lower-cased with every other character run folded to a dash, then a hash of the exact names, since the folding alone maps a-b/c and a/b-c to one key. Callers store it, so its bytes are a contract and never change.
func Observe ¶
Observe records resp's quota for CurrentRate, then returns RateLimited(resp). It is one check function for any client making its own GitHub requests: it fits httpkit.DoCapped and the download package's Options.Check.
func RateLimited ¶
RateLimited returns a *httpkit.RateLimitError when resp is GitHub's limit answer, else nil: a 403 or 429 with Retry-After or X-Ratelimit-Remaining "0". Reset comes from Retry-After, else X-Ratelimit-Reset.
func Select ¶
func Select(all []Release, version string, prerelease bool, keep func(Asset) bool) (Release, []Asset, error)
Select picks the newest non-draft release whose tag is version (any when version is empty) and returns it with its assets that keep accepts (every asset when keep is nil). A prerelease is picked only when prerelease is true or its tag is named. A release with no kept asset is ErrNoAsset, not a reason to look at an older one.
func Shape ¶
Shape is an asset's name with its extension and every version number removed, so the same download keeps its shape from one release to the next ("MyMod-1.2.0-linux.zip" and "MyMod-v1.3-linux.zip" are both "mymod linux"). Callers store it to remember a choice, so its output is a contract and never changes.
func ValidRepo ¶
ValidRepo reports a GitHub "owner/repo". It also refuses "." and "..", which the pattern admits but which would move an API URL's path.
Types ¶
type Asset ¶
type Asset struct {
ID int64 `json:"id"`
Name string `json:"name"`
ContentType string `json:"content_type"`
Size int64 `json:"size"`
URL string `json:"browser_download_url"`
Downloads int `json:"download_count"`
// Digest is "sha256:<hex>" when GitHub computed one for the asset.
Digest string `json:"digest,omitempty"`
}
Asset is one downloadable file of a release.
func Installable ¶
Installable narrows a release's assets to the ones a user of goos would install: source archives, checksum files and builds for other platforms drop out. A filter that would leave nothing is skipped, since an author who only ships, say, a Windows-named zip still means it for every platform.
type Auth ¶
type Auth struct {
// Run prints the token; nil means no token. GHCLI is the user's `gh` login.
Run func(ctx context.Context) ([]byte, error)
// contains filtered or unexported fields
}
Auth lends GitHub requests a token. The zero Auth sends none. A failed or empty Run also sends none. The token is never logged or put into an error.
type Client ¶
type Client struct {
// HTTP is the client every request goes through; nil means http.DefaultClient.
HTTP *http.Client
// CacheDir holds the cache files; "" disables the cache, so every call fetches.
CacheDir string
// APIBase overrides https://api.github.com, for tests and GitHub Enterprise.
APIBase string
// Now is the clock; nil means time.Now().UTC().
Now func() time.Time
}
Client reads releases through an on-disk cache.
func (*Client) CachedReleases ¶
CachedReleases lists owner/repo's releases from the stored answer however old it is, never asking GitHub. all is whether the list holds every release the repository has, which a full page may not; ok needs the current cache format.
func (*Client) ReleaseNotes ¶
ReleaseNotes returns the markdown body of owner/repo's release tagged tag. A published release never changes its notes, so a cached answer is final and never refetched; a failed lookup is not cached. An invalid owner/repo is ErrInvalidRepo, and a tag that is empty or would move the URL's path is ErrNoRelease, both with no request.
func (*Client) Releases ¶
Releases lists owner/repo's newest releases, as GitHub orders them. An answer younger than an hour comes from the cache and a stale one stands in when the lookup fails, rate limit included. An owner/repo that is not a GitHub name is ErrInvalidRepo, with no request.
func (*Client) ReleasesBetween ¶
func (c *Client) ReleasesBetween(ctx context.Context, owner, repo, installed, latest string) ([]Release, error)
ReleasesBetween returns owner/repo's published releases strictly newer than installed and not newer than latest (any when latest is empty), in GitHub's order (newest published first), at most MaxChangelog. A tag that is not a version is skipped. The order is publish order, not version order, so a backport published later still shows. It reads the cached releases list.
type Rate ¶
type Rate struct {
Known bool `json:"known"`
Limit int `json:"limit"`
Remaining int `json:"remaining"`
Reset time.Time `json:"reset"`
}
Rate is the quota GitHub last reported on a response.
func CurrentRate ¶
func CurrentRate() Rate
CurrentRate is the quota from the most recent GitHub response Observe saw that carried one.
type Release ¶
type Release struct {
Tag string `json:"tag_name"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
Body string `json:"body,omitempty"`
Published string `json:"published_at,omitempty"`
Assets []Asset `json:"assets"`
}
Release is one entry of a repository's releases list.
func FetchReleases ¶
FetchReleases reads one releases list from url (any /repos/{o}/{r}/releases page). A limit answer is a *httpkit.RateLimitError; any other non-200 a *httpkit.StatusError; a body over 8 MiB safefs.ErrTooLarge. Redirects to plain http are refused.