github

package
v0.0.0-...-d27829b Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

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

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

type APIError struct {
	Status int
	Body   string
}

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.

func (*APIError) Error

func (e *APIError) Error() string

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

func NewClient(cfg Config) (*Client, error)

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

func (c *Client) GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)

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

func (c *Client) GetInstallation(ctx context.Context, installationID int64) (Installation, error)

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

func (c *Client) InstallURL() string

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

func (c *Client) ListRepos(ctx context.Context, installationID int64) ([]Repo, error)

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

func (c *Client) RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)

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.

func (*Client) Slug

func (c *Client) Slug() string

Slug is the configured app slug (used to build install URLs).

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

type Installation struct {
	ID           int64  `json:"id"`
	AccountLogin string `json:"accountLogin"`
}

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

func (s *Service) Connect(ctx context.Context, installationID int64) (Connection, error)

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

func (s *Service) Disconnect(ctx context.Context, ownerID string) error

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

func (s *Service) GetConnection(ctx context.Context, ownerID string) (Connection, error)

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

func (s *Service) GraphQLMutation() graphql.Fields

GraphQLMutation returns connectGit (returns the connection + install URL) and disconnectGit.

func (*Service) GraphQLQuery

func (s *Service) GraphQLQuery() graphql.Fields

GraphQLQuery returns the gitConnection + repos queries.

func (*Service) ListBranches

func (s *Service) ListBranches(ctx context.Context, ownerID, repoURL string) ([]string, error)

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

func (s *Service) ListRepos(ctx context.Context, ownerID string) ([]Repo, error)

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

func (s *Service) RegisterMCP(srv *mcp.Server)

RegisterMCP adds the git-connect tools to the shared MCP server.

func (*Service) RegisterREST

func (s *Service) RegisterREST(mux *http.ServeMux)

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

func (s *Service) StartConnect(ctx context.Context, ownerID string) (Connection, error)

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.

Jump to

Keyboard shortcuts

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