github

package
v0.2.9 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AppAuth

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

AppAuth manages GitHub App installation tokens with automatic refresh. Installation tokens expire after 1 hour; this refreshes at 45 minutes.

func NewAppAuth

func NewAppAuth(appID, installationID int64, keyPath string, log *slog.Logger) (*AppAuth, error)

NewAppAuth loads the PEM key and returns an AppAuth that mints and refreshes GitHub App installation tokens.

func (*AppAuth) Stop

func (a *AppAuth) Stop()

Stop halts the background refresh goroutine.

func (*AppAuth) Token

func (a *AppAuth) Token() string

Token returns the current valid installation token, refreshing it synchronously if the cached token expires within 5 minutes. The background refreshLoop is still the primary path; this is a safety net so a missed background refresh doesn't cause repeated 401s for hours.

type Client

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

Client handles GitHub API interactions and webhook events.

func New

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

New creates a GitHub client. Uses AppAuth for dynamic token refresh when configured, otherwise a static PAT.

All GitHub API responses flow through rateTrackingTransport, which parses the X-RateLimit-* headers and updates the ephemerd_github_api_rate_* gauges. Operators use ephemerd_github_api_rate_updated_seconds to tell a current-but-exhausted budget from a stale reading.

func (*Client) CleanStaleWebhooks

func (c *Client) CleanStaleWebhooks(ctx context.Context)

CleanStaleWebhooks removes any workflow_job webhooks left behind by previous ephemerd instances that crashed or were killed without cleanup. Called on startup before registering new webhooks to avoid hitting GitHub's 20-hook limit.

Skipped entirely in pool mode: this sweep deletes every workflow_job hook it can see, and in a pooled fleet those are live hooks belonging to pool-mates (or other pools). Adoption in RegisterWebhooks makes the sweep unnecessary for pooled nodes — same-URL hooks are reused, not duplicated.

func (*Client) DeregisterWebhooks

func (c *Client) DeregisterWebhooks(ctx context.Context, hooks []ManagedWebhook)

DeregisterWebhooks removes all managed webhooks. Called on shutdown. No-op in pool mode: the hook is shared with pool-mates that are still serving; the pool's lifecycle owner (e.g. mayfly destroying the pool) removes it when the last member goes away.

func (*Client) FetchJobImage

func (c *Client) FetchJobImage(ctx context.Context, repo string, runID int64, jobID int64) string

FetchJobImage fetches the workflow run's job definition and reads the container image declared in the job's `container:` field. This requires an extra API call per job but lets users specify the image directly in their workflow:

jobs:
  build:
    runs-on: [self-hosted, linux, x64]
    container:
      image: ghcr.io/myorg/custom-build:latest
  quick:
    runs-on: [self-hosted, linux, x64]
    container: ghcr.io/myorg/quick:latest  # shorthand

Returns empty string if no container image is set.

func (*Client) IsOrgLevel

func (c *Client) IsOrgLevel() bool

IsOrgLevel returns true when no repos are configured, meaning ephemerd registers runners at the organization level (available to all repos).

func (*Client) Owner added in v0.1.3

func (c *Client) Owner() string

Owner returns the GitHub org/user this client is scoped to.

func (*Client) PingWebhook

func (c *Client) PingWebhook(ctx context.Context, m ManagedWebhook) error

PingWebhook triggers a ping event for a managed webhook.

func (*Client) PollJobs

func (c *Client) PollJobs(ctx context.Context) ([]JobEvent, error)

PollJobs checks for queued workflow jobs targeting self-hosted runners. If repos are configured, polls those repos. Otherwise, polls all repos in the org.

func (*Client) RateSnapshot

func (c *Client) RateSnapshot() (remaining, limit int64, reset, updated time.Time)

RateSnapshot returns the last-observed rate-limit state. The scheduler uses this to bias retry backoff — when remaining==0 and now < reset, the next attempt is scheduled just after reset instead of blindly falling through the exponential-backoff ladder. Fields are zero-valued when the client hasn't yet made a request or was created via the bare test constructor.

func (*Client) RegisterJITRunner

func (c *Client) RegisterJITRunner(ctx context.Context, repo string, name string, labels []string) (*gh.JITRunnerConfig, error)

RegisterJITRunner creates a just-in-time runner. If repos are configured, registers at the repo level. If repos are empty, registers at the org level (available to all repos in the org).

func (*Client) RegisterWebhooks

func (c *Client) RegisterWebhooks(ctx context.Context, webhookURL, secret string) ([]ManagedWebhook, error)

RegisterWebhooks creates workflow_job webhooks on each configured repo (or the org) pointing at the given URL with the given secret. Returns the managed hooks for cleanup.

It is idempotent: before creating a hook it lists the existing hooks and, if one already points at webhookURL, reuses it instead of creating a duplicate. GitHub rejects a duplicate hook config with HTTP 422, so without this the external-tunnel path (which re-registers on every startup) would fail on the second boot.

In pool mode the reuse is upgraded to adopt: the existing hook (registered by a pool-mate) is edited in place so secret/events/active converge on this member's config — pool members must present one shared secret. A create that still races a pool-mate to a 422 resolves by adopting the winner's hook.

func (*Client) RemoveRunner

func (c *Client) RemoveRunner(ctx context.Context, repo string, runnerID int64) error

RemoveRunner removes a self-hosted runner by ID. Uses org-level or repo-level API depending on configuration.

func (*Client) RunnerBusy added in v0.2.9

func (c *Client) RunnerBusy(ctx context.Context, repo string, runnerID int64) (bool, error)

RunnerBusy reports whether a self-hosted runner is currently executing a job, from GitHub's own per-runner `busy` flag.

Uses the org-level or repo-level API depending on configuration, the same way RemoveRunner does — a JIT runner registered at the org level is not addressable through the repo endpoint.

A runner that has already deregistered itself (404) is reported as not busy: an ephemeral runner removes itself after finishing its job, so "gone" is the strongest possible evidence that nothing is running on it. Every other error is returned to the caller, which must treat it as "could not determine" rather than as idle.

func (*Client) SetHTTPClient

func (c *Client) SetHTTPClient(ghClient *gh.Client)

SetHTTPClient replaces the underlying go-github client. Used by test infrastructure to point at a fake server.

func (*Client) WebhookHandler

func (c *Client) WebhookHandler(secret string) (http.Handler, <-chan JobEvent)

WebhookHandler returns an http.Handler that processes workflow_job webhook events. Events are sent to the returned channel.

type Config

type Config struct {
	Token string
	Owner string
	Repos []string
	Log   *slog.Logger

	// App auth (used instead of Token when set)
	AppAuth *AppAuth

	// PoolMode changes webhook lifecycle semantics for nodes sharing one
	// public webhook URL: registration adopts an existing same-URL hook,
	// deregistration is a no-op, and the stale-hook sweep is skipped.
	PoolMode bool

	// AllowedRepos is an OPTIONAL dispatch allowlist. When non-empty, only jobs
	// whose repository name is in this list may dispatch a runner. Empty (the
	// default) preserves the historical behavior of dispatching for any tracked
	// repo. Defense-in-depth beneath GitHub's outside-collaborator approval.
	AllowedRepos []string

	// RequiredLabels is an OPTIONAL dispatch allowlist. When non-empty, an
	// incoming job must carry at least one of these labels (in addition to the
	// mandatory self-hosted gate) before it dispatches. Empty imposes no extra
	// label requirement.
	RequiredLabels []string
}

Config for the GitHub client.

type JobEvent

type JobEvent struct {
	Action string
	Repo   string
	Job    *gh.WorkflowJob
}

JobEvent is emitted when a workflow_job webhook fires.

type ManagedWebhook

type ManagedWebhook struct {
	Repo   string // empty for org-level
	HookID int64
}

ManagedWebhook tracks a webhook created by ephemerd so it can be cleaned up on shutdown.

Jump to

Keyboard shortcuts

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