Documentation
¶
Overview ¶
Package forgerpc implements a lightweight ConnectRPC client for the Gitea/Forgejo RunnerService protocol.
Both platforms share the same runner.v1.RunnerService API (Register, Declare, FetchTask) with minor response differences (Forgejo returns a runner UUID). This package uses raw HTTP + JSON encoding to avoid pulling in the full ConnectRPC and protobuf dependency trees.
Wire format: Connect protocol unary RPCs over HTTP/1.1 with JSON.
POST {instanceURL}/api/actions/runner.v1.RunnerService/{Method}
Content-Type: application/json
Connect-Protocol-Version: 1
Auth (post-registration):
x-runner-uuid: {uuid}
x-runner-token: {token}
Reference:
- Gitea proto: https://gitea.com/gitea/actions-proto-def
- Forgejo proto: https://code.forgejo.org/forgejo/actions-proto
- Connect spec: https://connectrpc.com/docs/protocol
Index ¶
- func DeclareLabels(registerLabels []string) []string
- type Client
- func (c *Client) Declare(ctx context.Context, labels []string) error
- func (c *Client) FetchTask(ctx context.Context, tasksVersion int64) (*FetchTaskResult, error)
- func (c *Client) Register(ctx context.Context, name, regToken, version string, labels []string) (*Runner, error)
- func (c *Client) RegistrationToken(ctx context.Context, apiToken, owner, repo string) (string, error)
- func (c *Client) SetAuth(uuid, token string)
- func (c *Client) Token() string
- func (c *Client) UUID() string
- func (c *Client) UpdateLog(ctx context.Context, taskID int64, index int64, rows []LogRow, noMore bool) (int64, error)
- func (c *Client) UpdateTask(ctx context.Context, state *TaskState, outputs map[string]string) (*UpdateTaskResponse, error)
- func (c *Client) Version(ctx context.Context) (string, error)
- type FetchTaskResult
- type LogRow
- type RPCError
- type Runner
- type StepState
- type Task
- type TaskResult
- type TaskState
- type UpdateTaskResponse
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DeclareLabels ¶
DeclareLabels converts Register-format labels ("ubuntu-latest:docker://image") to Declare-format labels ("ubuntu-latest") by stripping the :type://image suffix.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to a Gitea or Forgejo instance's RunnerService via ConnectRPC.
func NewClient ¶
NewClient creates a ConnectRPC client for the given forge instance URL. Pass nil for httpClient to use a default with 30s timeout.
func (*Client) Declare ¶
Declare announces the runner's labels to the server. Call after Register to tell the forge which job labels this runner handles.
Labels should be name-only strings (e.g. "ubuntu-latest"), NOT the full docker-mapped format used in Register ("ubuntu-latest:docker://image"). Forgejo matches runs-on: values against Declare labels, so "ubuntu-latest" must match exactly. Use DeclareLabels to convert from Register format.
func (*Client) FetchTask ¶
FetchTask polls for an available task. Returns a nil Task if none available. The server may long-poll (~5s) before returning an empty response.
Pass tasksVersion from the previous response for efficient change detection; use 0 for the first call.
func (*Client) Register ¶
func (c *Client) Register(ctx context.Context, name, regToken, version string, labels []string) (*Runner, error)
Register exchanges a registration token for persistent runner credentials. On success the client stores the credentials for subsequent authenticated calls.
Labels are plain strings (e.g. "ubuntu-latest:docker://node:20-bookworm"). The proto field is repeated string, not repeated AgentLabel — use Declare for the structured label announcement after registration.
func (*Client) RegistrationToken ¶
func (c *Client) RegistrationToken(ctx context.Context, apiToken, owner, repo string) (string, error)
RegistrationToken fetches a runner registration token from the forge REST API.
Scope is determined by owner/repo:
- owner="" repo="" → instance-level (admin)
- owner="org" repo="" → org-level
- owner="org" repo="repo" → repo-level
The HTTP method for this endpoint diverged across Gitea versions:
Gitea <1.22: endpoint does not exist (runners registered via CLI/admin UI) Gitea 1.22–1.23: GET only (POST returns 405) Gitea 1.24–1.25: both GET and POST Gitea 1.26+: POST only (GET returns 404) Forgejo (all): GET
This method tries POST first then falls back to GET, covering all versions.
func (*Client) SetAuth ¶
SetAuth sets the runner credentials for authenticated RPCs. Called automatically by Register; only needed when restoring saved credentials.
func (*Client) UpdateLog ¶
func (c *Client) UpdateLog(ctx context.Context, taskID int64, index int64, rows []LogRow, noMore bool) (int64, error)
UpdateLog streams log lines to the forge. Returns the server's acknowledged line index. Call with noMore=true after the final flush.
func (*Client) UpdateTask ¶
func (c *Client) UpdateTask(ctx context.Context, state *TaskState, outputs map[string]string) (*UpdateTaskResponse, error)
UpdateTask reports the current state of a task (step results, completion).
type FetchTaskResult ¶
type FetchTaskResult struct {
Task *Task // nil if no task available
TasksVersion int64 // pass back to next FetchTask for change detection
}
FetchTaskResult is the response from FetchTask.
func (*FetchTaskResult) UnmarshalJSON ¶
func (r *FetchTaskResult) UnmarshalJSON(data []byte) error
type RPCError ¶
type RPCError struct {
HTTPStatus int
Code string // ConnectRPC error code (e.g. "unauthenticated", "not_found")
Message string
}
RPCError is returned when the ConnectRPC endpoint returns a non-200 status.
type StepState ¶
type StepState struct {
Step int64 `json:"step"`
Result TaskResult `json:"result"`
StartedAt *time.Time `json:"startedAt,omitempty"`
StoppedAt *time.Time `json:"stoppedAt,omitempty"`
LogIndex int64 `json:"logIndex"`
LogLength int64 `json:"logLength"`
}
StepState reports the outcome of a single workflow step.
type Task ¶
type Task struct {
ID int64
UUID string // Forgejo returns this; Gitea may leave empty
WorkflowPayload string // base64-encoded workflow YAML
Context json.RawMessage // google.protobuf.Struct — repo/ref/secrets context
}
Task is a CI task returned by FetchTask.
func (*Task) ContainerImage ¶
ContainerImage parses the workflow YAML and returns the first job's `container.image` value, if set. Returns "" if not found.
func (*Task) Repo ¶
Repo extracts the repository full name (e.g. "owner/repo") from the task context. Gitea/Forgejo nest repo info under a "github" key for Actions compatibility.
func (*Task) UnmarshalJSON ¶
func (*Task) WorkflowYAML ¶
WorkflowYAML decodes the base64 workflow payload to raw YAML bytes.
type TaskResult ¶
type TaskResult int
TaskResult represents the outcome of a task or step.
const ( ResultUnspecified TaskResult = 0 ResultSuccess TaskResult = 1 ResultFailure TaskResult = 2 ResultCancelled TaskResult = 3 ResultSkipped TaskResult = 4 )
type TaskState ¶
type TaskState struct {
ID int64 `json:"id"`
Result TaskResult `json:"result"`
StartedAt *time.Time `json:"startedAt,omitempty"`
StoppedAt *time.Time `json:"stoppedAt,omitempty"`
Steps []StepState `json:"steps,omitempty"`
}
TaskState reports the current state of a task.
type UpdateTaskResponse ¶
type UpdateTaskResponse struct {
State *TaskState `json:"state,omitempty"`
SentOutputs []string `json:"sentOutputs,omitempty"`
}
UpdateTaskResponse is the response from UpdateTask.