92 lines
3.3 KiB
Go
92 lines
3.3 KiB
Go
// Package runnerbootstrap configures an unmodified, one-shot Gitea Runner to
|
|
// consume exactly the task assigned by the controller facade.
|
|
package runnerbootstrap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"git.ddupan.top/panxiao81/gitea-dynamic-runner/internal/runnerfacade"
|
|
"git.ddupan.top/panxiao81/gitea-dynamic-runner/internal/taskassignment"
|
|
)
|
|
|
|
const (
|
|
EnvAssignmentID = "CI_ASSIGNMENT_ID"
|
|
EnvCapability = "CI_RUNNER_CAPABILITY"
|
|
EnvFacadeURL = "CI_RUNNER_FACADE_URL"
|
|
EnvFacadeID = "CI_RUNNER_FACADE_SPIFFE_ID"
|
|
EnvSPIFFEID = "CI_SPIFFE_ID"
|
|
EnvBackend = "CI_RUNNER_BACKEND"
|
|
)
|
|
|
|
// Bootstrap emits assignment-scoped launch configuration. FacadeURL is the
|
|
// controller endpoint reached by the local SPIFFE proxy, not by Runner itself.
|
|
type Bootstrap struct {
|
|
Capabilities runnerfacade.Capabilities
|
|
FacadeURL string
|
|
FacadeSPIFFEID string
|
|
WorkloadAPIAddr string
|
|
}
|
|
|
|
func (b Bootstrap) Environment(assignment taskassignment.Assignment) (map[string]string, error) {
|
|
if assignment.ID == "" || assignment.Identity.SPIFFEID == "" || b.FacadeSPIFFEID == "" || b.WorkloadAPIAddr == "" {
|
|
return nil, errors.New("assignment ID and SPIFFE ID are required")
|
|
}
|
|
parsed, err := url.Parse(b.FacadeURL)
|
|
if err != nil || parsed.Scheme != "https" || parsed.Host == "" {
|
|
return nil, fmt.Errorf("runner facade URL must be an absolute https URL")
|
|
}
|
|
capability := b.Capabilities.Issue(assignment.ID)
|
|
if capability == "" {
|
|
return nil, errors.New("runner capability issuer is not configured")
|
|
}
|
|
return map[string]string{
|
|
EnvAssignmentID: assignment.ID,
|
|
EnvCapability: capability,
|
|
EnvFacadeURL: b.FacadeURL,
|
|
EnvFacadeID: b.FacadeSPIFFEID,
|
|
EnvSPIFFEID: assignment.Identity.SPIFFEID,
|
|
EnvBackend: string(assignment.Backend),
|
|
"SPIFFE_ENDPOINT_SOCKET": b.WorkloadAPIAddr,
|
|
}, nil
|
|
}
|
|
|
|
// Registration mirrors Gitea Runner v3.5.0's registration file schema. ID is
|
|
// intentionally zero: the facade authenticates UUID and token and never uses
|
|
// the server-issued runner database ID.
|
|
type Registration struct {
|
|
Warning string `json:"WARNING"`
|
|
ID int64 `json:"id"`
|
|
UUID string `json:"uuid"`
|
|
Name string `json:"name"`
|
|
Token string `json:"token"`
|
|
Address string `json:"address"`
|
|
Labels []string `json:"labels"`
|
|
Ephemeral bool `json:"ephemeral"`
|
|
}
|
|
|
|
func RegistrationJSON(assignmentID, capability, localProxyURL string, backend taskassignment.Backend) ([]byte, error) {
|
|
if assignmentID == "" || capability == "" {
|
|
return nil, errors.New("assignment ID and runner capability are required")
|
|
}
|
|
parsed, err := url.Parse(localProxyURL)
|
|
if err != nil || parsed.Scheme != "http" || parsed.Host == "" {
|
|
return nil, errors.New("local runner proxy URL must be an absolute http URL")
|
|
}
|
|
if backend != taskassignment.BackendPod && backend != taskassignment.BackendVM {
|
|
return nil, fmt.Errorf("unsupported runner backend %q", backend)
|
|
}
|
|
registration := Registration{
|
|
Warning: "Generated for one preassigned task by gitea-dynamic-runner.",
|
|
UUID: assignmentID, Name: assignmentID, Token: capability,
|
|
Address: localProxyURL, Labels: []string{"self-hosted", string(backend)}, Ephemeral: true,
|
|
}
|
|
data, err := json.MarshalIndent(registration, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(data, '\n'), nil
|
|
}
|