Documentation
¶
Overview ¶
Package github is bex-api's GitHub App integration (docs/ADR026-github-integration.md): a small client that signs the app JWT, mints short-lived installation tokens, and lists an installation's repositories — plus the workspace-connection service verbs over the control-plane store. The operator never imports this; bex-api mints tokens and writes them into a k8s Secret the build Job consumes.
Index ¶
- type APIClient
- type APIError
- type Client
- func (c *Client) GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)
- func (c *Client) GetInstallation(ctx context.Context, installationID int64) (Installation, error)
- func (c *Client) InstallURL() string
- func (c *Client) ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
- func (c *Client) ListRepos(ctx context.Context, installationID int64) ([]Repo, error)
- func (c *Client) MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
- func (c *Client) RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)
- func (c *Client) Slug() string
- type Commit
- type Config
- type Connection
- type ConnectionStore
- type Installation
- type InstallationToken
- type Repo
- type Service
- func (s *Service) Connect(ctx context.Context, installationID int64) (Connection, error)
- func (s *Service) DeployCommitSource() commitSource
- func (s *Service) DeployTokenSource() tokenSource
- func (s *Service) Disconnect(ctx context.Context, ownerID string) error
- func (s *Service) GetConnection(ctx context.Context, ownerID string) (Connection, error)
- func (s *Service) GraphQLMutation() graphql.Fields
- func (s *Service) GraphQLQuery() graphql.Fields
- func (s *Service) ListBranches(ctx context.Context, ownerID, repoURL string) ([]string, error)
- func (s *Service) ListRepos(ctx context.Context, ownerID string) ([]Repo, error)
- func (s *Service) RegisterMCP(srv *mcp.Server)
- func (s *Service) RegisterREST(mux *http.ServeMux)
- func (s *Service) StartConnect(ctx context.Context, ownerID string) (Connection, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type APIClient ¶
type APIClient interface {
InstallURL() string
GetInstallation(ctx context.Context, installationID int64) (Installation, error)
ListRepos(ctx context.Context, installationID int64) ([]Repo, error)
ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)
GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)
}
APIClient is the GitHub REST surface the Service uses — *Client in production, a fake in tests. nil => the GitHub App is unconfigured (BEX_GITHUB_APP_* unset) and every verb reports core.ErrGitHubUnavailable.
type APIError ¶
APIError is a non-2xx GitHub response. Callers map it to a clean bex error (never a raw 500) so a GitHub outage surfaces as "GitHub said N", not a panic.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the GitHub REST API as a GitHub App. It is safe for concurrent use.
func NewClient ¶
NewClient parses the config (numeric app id, PEM private key) and returns a ready client. It errors if any field is missing or malformed.
func (*Client) GetCommit ¶
GetCommit resolves ref (a branch name, tag, or SHA) to the exact commit it points at (GET /repos/{owner}/{repo}/commits/{ref}). An unknown ref or an out-of-grant repo returns an *APIError (404/422) — callers treat those as "unresolvable", not a failure.
func (*Client) GetInstallation ¶
GetInstallation fetches one installation by id (GET /app/installations/{id}), authenticated as the app. A non-existent or inaccessible id returns an *APIError (typically 404) — the authenticity check for the connect callback.
func (*Client) InstallURL ¶
InstallURL is where a workspace admin installs the app (and grants repos).
func (*Client) ListBranches ¶
func (c *Client) ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
ListBranches returns every branch name of owner/repo the installation can access, following pagination (per_page=100, `Link` rel="next"). It mints a fresh installation token first. (w5/m54 — feeds the dashboard's searchable Branch combobox.)
func (*Client) ListRepos ¶
ListRepos returns every repository the installation can access, following pagination (per_page=100, GitHub `Link` header rel="next"). It mints a fresh installation token first.
func (*Client) MintInstallationToken ¶
func (c *Client) MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
MintInstallationToken exchanges the app JWT for a 1h installation access token for the given installation.
func (*Client) RepoAccessible ¶
RepoAccessible reports whether the given installation token can reach owner/repo (GET /repos/{owner}/{repo}). An installation token only reaches repos in the installation's grant, so 404 => not granted (ok=false, no error); 2xx => granted; any other status is an *APIError.
type Commit ¶
type Commit struct {
SHA string `json:"sha"`
Message string `json:"message"`
AuthorAt *time.Time `json:"authorAt,omitempty"`
}
Commit is the resolved tip of a ref — the SHA plus its message and author timestamp (w9/001 + w2/m42). The subset of GitHub's commit object the deploy path stamps onto a deploy row as provenance.
type Config ¶
type Config struct {
AppID string // numeric GitHub App id (the JWT `iss`)
PrivateKey string // RSA private key, PEM (out-of-band secret)
Slug string // app slug, builds the install URL
}
Config is the GitHub App configuration read once at startup from BEX_GITHUB_APP_ID / BEX_GITHUB_APP_PRIVATE_KEY / BEX_GITHUB_APP_SLUG. Any field empty (or an unparseable id/key) => NewClient errors and the caller leaves the github service unconfigured (503).
type Connection ¶
type Connection struct {
Connected bool `json:"connected"`
AccountLogin string `json:"accountLogin,omitempty"`
InstallationID int64 `json:"installationId,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
InstallURL string `json:"installUrl"`
}
Connection is the neutral connection view every adapter renders. InstallURL is always populated (the connect CTA the human clicks); the rest are set only when Connected.
type ConnectionStore ¶
type ConnectionStore interface {
UpsertGitConnection(ctx context.Context, c store.GitConnection) (store.GitConnection, error)
GetGitConnection(ctx context.Context, workspaceID string) (store.GitConnection, error)
DeleteGitConnection(ctx context.Context, workspaceID string) error
}
ConnectionStore is the Service's seam to the control-plane store — the narrow slice of Store it needs. *store.PGStore satisfies it; a fake backs the tests. nil => the control-plane store is off (BEX_CP_DB_URI unset) and every verb reports core.ErrGitHubUnavailable.
type Installation ¶
Installation identifies a GitHub App installation and the account it belongs to. Fetched with the app JWT, so a forged/unknown id fails authentication — which is how Connect validates a browser-supplied installation_id.
type InstallationToken ¶
type InstallationToken struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
}
InstallationToken is a short-lived (1h) installation access token.
type Repo ¶
type Repo struct {
ID int64 `json:"id"`
FullName string `json:"fullName"`
Private bool `json:"private"`
DefaultBranch string `json:"defaultBranch"`
HTMLURL string `json:"htmlUrl"`
CloneURL string `json:"cloneUrl"`
}
Repo is the subset of a GitHub repository the repo picker and deploy path need. JSON tags are the bex-api camelCase shape (identical across surfaces), not GitHub's snake_case wire shape (decoded via ghRepo below).
type Service ¶
type Service struct {
*core.Base
GitHub APIClient // nil => GitHub App unconfigured
Store ConnectionStore // nil => control-plane store off
// StateSecret signs the short-lived workspace credential carried through the
// browser install redirect. Production reuses BEX_GITHUB_APP_PRIVATE_KEY's
// PEM bytes, so no second platform secret or replica-local state is needed.
StateSecret []byte
// DashboardURL is BEX_DASHBOARD_URL — where the install callback redirects
// the browser after success or with a bounded failure code. Empty => the
// callback returns JSON instead of redirecting.
DashboardURL string
}
Service manages a workspace's GitHub App connection and lists its repos over the injected client + store. Both seams must be present; either nil => 503.
func (*Service) Connect ¶
Connect records (or replaces) the workspace's GitHub App installation. It validates the browser-supplied installation id by fetching it from GitHub with the app JWT first (a forged id 404s, mapped to ErrBadRequest), then upserts the connection. Admin-only.
func (*Service) DeployCommitSource ¶
func (s *Service) DeployCommitSource() commitSource
DeployCommitSource returns the deploy path's commit-resolution seam (wired onto deploys.Service and apps.Service in the composition root).
func (*Service) DeployTokenSource ¶
func (s *Service) DeployTokenSource() tokenSource
DeployTokenSource returns the deploy path's clone-token seam (wired onto apps.Service in the composition root).
func (*Service) Disconnect ¶
Disconnect removes ownerID's connection ("" => the caller's default workspace, w6/m18). Idempotent: disconnecting when not connected is a no-op success. Admin-only.
func (*Service) GetConnection ¶
GetConnection returns ownerID's connection status ("" => the caller's default workspace, w6/m18). "Not connected" is a valid state (Connected:false + the install URL), not an error. Member read.
func (*Service) GraphQLMutation ¶
GraphQLMutation returns connectGit (returns the connection + install URL) and disconnectGit.
func (*Service) GraphQLQuery ¶
GraphQLQuery returns the gitConnection + repos queries.
func (*Service) ListBranches ¶
ListBranches returns the branch names of repoURL for ownerID's connected installation ("" => the caller's default workspace). It degrades to an empty list — never an error — for a non-github.com repo, no connection, or a repo the installation can't see, so the dashboard falls back to free-text branch entry (w5/m54). Member read.
func (*Service) ListRepos ¶
ListRepos returns ownerID's connected installation's repositories ("" => the caller's default workspace, w6/m18; private included). With no connection the list is empty (disconnect "empties" the repos), not an error. Member read.
func (*Service) RegisterMCP ¶
RegisterMCP adds the git-connect tools to the shared MCP server.
func (*Service) RegisterREST ¶
RegisterREST mounts the GitHub-connect surface. `GET /v1/repos` and the connection verbs are bex extensions (Render exposes repos only via its private dashboard API); naming follows Render's kebab-case noun style. The callback is GitHub's post-install "Setup URL" redirect target. Browser callbacks carry a short-lived signed state credential instead of a dashboard cookie; API callers may keep using their ordinary Bearer/session credential. In both cases the installation_id is validated against GitHub before anything is recorded (docs/ADR026-github-integration.md).
func (*Service) StartConnect ¶
StartConnect returns the current connection state plus the install URL the admin clicks to install the app (and grant repos). Admin-only — connecting a workspace's GitHub is an admin action even though the record lands at the callback. ownerID ("" => the caller's default workspace, w6/m18) names the workspace to check/connect, membership-checked via core.WithWorkspace like every other explicit-target verb. The returned install URL carries that resolved workspace in a short-lived signed state credential, so GitHub's identity-less callback can safely record against the same workspace.