github

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package github is the conductor's client of a GitHub App: it turns an App id and a private key into the short-lived installation tokens a turn clones, fetches and pushes with.

Why an App rather than the personal access token a playbook's `secrets:` can already carry: an installation token lasts an hour, is scoped to named repositories, belongs to no human, and is revoked centrally by uninstalling the App. A PAT is none of those — it is long-lived, it carries one person's access, and revoking it is somebody remembering to.

The cost is that a token has to be MINTED, and minting needs the private key. That key can mint a token for every repository the App is installed on, so it is strictly more valuable than the PAT it replaces and it lives in exactly one place: the conductor's own host, beside the master key. Nothing hands it to a task. See docs/security.md.

Nothing here caches a token past its usefulness and nothing here logs one.

Index

Constants

View Source
const DefaultBaseURL = "https://api.github.com"

DefaultBaseURL is github.com's API. GitHub Enterprise Server lives on another host and is not supported: Podium speaks to one GitHub, and pretending otherwise would be a configuration knob with no tested path behind it.

View Source
const DefaultTimeout = 15 * time.Second

DefaultTimeout bounds one call. Minting is two round trips at worst and both are small.

Variables

View Source
var ErrNotFound = errors.New("github: not found")

ErrNotFound is a 404. It is internal to this package's own retry between the org and user installation endpoints; callers see ErrNotInstalled instead.

View Source
var ErrNotInstalled = errors.New("the app is not installed on that account")

ErrNotInstalled is a repository owner the App is not installed on. It means the operator created the App and never installed it, or installed it on another account — the most common way this is set up wrong, and unrecoverable without a human.

View Source
var ErrUnauthorized = errors.New("github refused the app credential")

ErrUnauthorized is GitHub refusing the App's own credential: a wrong app id, a key that does not match it, or an App that has been deleted. It is separated because it is the one failure an operator fixes by editing configuration rather than by reading a log.

Functions

func ParsePrivateKey

func ParsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error)

ParsePrivateKey reads the PEM GitHub hands out. GitHub generates PKCS#1 ("BEGIN RSA PRIVATE KEY"); PKCS#8 is accepted too, because a key round-tripped through openssl or a secret manager often comes back in that form and refusing it would be a puzzle rather than a message.

func SplitRepoURL

func SplitRepoURL(raw string) (owner, repo string, err error)

SplitRepoURL takes a playbook's repos[].url apart into the owner and the bare repository name the token call wants.

It accepts what an operator actually writes — with or without .git, with or without a trailing slash — and refuses anything that is not github.com, because a token minted by this App is worthless anywhere else and a silent mismatch would show up as a 404 on the clone rather than as a message about the URL.

Types

type Client

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

Client mints installation tokens for one GitHub App.

It is safe for concurrent use and it caches: an installation id never changes, the bot identity never changes, and a token is reused until renewBefore of its expiry. A conductor running twenty turns of one playbook mints once, not twenty times, which is what keeps this off GitHub's rate limit.

func New

func New(opts Options) (*Client, error)

New returns a Client, or an error naming what is missing.

func (*Client) Identity

func (c *Client) Identity(ctx context.Context) (Identity, error)

Identity is the App's bot account, as an author. It is two calls the first time and cached for ever after: neither the slug nor the id of a bot account changes.

func (*Client) Token

func (c *Client) Token(ctx context.Context, owner string, repos []string) (Token, error)

Token is an installation token for exactly the named repositories of one owner.

repos are bare repository names ("monorepo"), not "owner/repo": GitHub's parameter is scoped to the installation, which already fixes the owner. An empty list is refused rather than sent, because GitHub reads "no repositories" as EVERY repository the installation has, and a turn that asked for nothing getting everything is the wrong way round for a default.

type Identity

type Identity struct {
	Name  string
	Email string
}

Identity is the App's bot account as git and GitHub see it.

The email is the whole point of the type. GitHub links a commit to an ACCOUNT by the author's email, and a bot account's address is `<user id>+<slug>[bot]@users.noreply. github.com` — an id nothing but the API will tell you. Get it wrong and every commit is attributed to nobody, which is the failure this whole path exists to avoid.

type Options

type Options struct {
	// AppID is the App's numeric id, as GitHub's settings page shows it.
	AppID string
	// PrivateKeyPEM is the PEM the App's "generate a private key" button produced.
	PrivateKeyPEM []byte
	// BaseURL overrides DefaultBaseURL. It exists for tests.
	BaseURL string
	// HTTPClient overrides the default. It exists for tests.
	HTTPClient *http.Client
	// Now overrides the clock. It exists for tests.
	Now func() time.Time
}

Options builds a Client.

type Token

type Token struct {
	Value     string
	ExpiresAt time.Time
}

Token is one installation token and the moment it stops working.

Jump to

Keyboard shortcuts

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