feat: bootstrap official runner through SPIFFE facade
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
func (b Bootstrap) Environment(assignment taskassignment.Assignment) (map[string]string, error) {
|
||||
if assignment.ID == "" || assignment.Identity.SPIFFEID == "" || b.FacadeSPIFFEID == "" {
|
||||
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),
|
||||
}, 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
|
||||
}
|
||||
Reference in New Issue
Block a user