117 lines
3.7 KiB
Go
117 lines
3.7 KiB
Go
package runnerbootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"git.ddupan.top/panxiao81/gitea-dynamic-runner/internal/taskassignment"
|
|
)
|
|
|
|
type ExecutorConfig struct {
|
|
AssignmentID string
|
|
Capability string
|
|
Backend taskassignment.Backend
|
|
FacadeURL string
|
|
FacadeSPIFFEID string
|
|
WorkloadAPIAddr string
|
|
RunnerBinary string
|
|
ListenAddress string
|
|
WorkDir string
|
|
Stdout *os.File
|
|
Stderr *os.File
|
|
}
|
|
|
|
// RunExecutor runs the SPIFFE proxy and one unmodified official Runner process.
|
|
// The generated registration file exists only in the executor's temporary
|
|
// work directory and the runner exits after its preassigned task.
|
|
func RunExecutor(ctx context.Context, config ExecutorConfig) error {
|
|
if config.RunnerBinary == "" {
|
|
config.RunnerBinary = "gitea-runner"
|
|
}
|
|
if config.ListenAddress == "" {
|
|
config.ListenAddress = "127.0.0.1:0"
|
|
}
|
|
listener, err := net.Listen("tcp", config.ListenAddress)
|
|
if err != nil {
|
|
return fmt.Errorf("listen for local runner proxy: %w", err)
|
|
}
|
|
defer listener.Close()
|
|
address, ok := listener.Addr().(*net.TCPAddr)
|
|
if !ok || !address.IP.IsLoopback() {
|
|
return errors.New("runner proxy must listen on a loopback address")
|
|
}
|
|
|
|
proxy, err := NewProxy(ctx, config.FacadeURL, config.FacadeSPIFFEID, config.WorkloadAPIAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer proxy.Close()
|
|
|
|
workDir := config.WorkDir
|
|
removeWorkDir := false
|
|
if workDir == "" {
|
|
workDir, err = os.MkdirTemp("", "gitea-task-runner-")
|
|
if err != nil {
|
|
return fmt.Errorf("create runner work directory: %w", err)
|
|
}
|
|
removeWorkDir = true
|
|
}
|
|
if removeWorkDir {
|
|
defer os.RemoveAll(workDir)
|
|
}
|
|
registration, err := RegistrationJSON(
|
|
config.AssignmentID, config.Capability, "http://"+listener.Addr().String(), config.Backend,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(filepath.Join(workDir, ".runner"), registration, 0o600); err != nil {
|
|
return fmt.Errorf("write one-shot runner registration: %w", err)
|
|
}
|
|
|
|
server := &http.Server{Handler: proxy.Handler, ReadHeaderTimeout: 10 * time.Second}
|
|
serverErrors := make(chan error, 1)
|
|
go func() { serverErrors <- server.Serve(listener) }()
|
|
|
|
command := exec.CommandContext(ctx, config.RunnerBinary, "daemon", "--once")
|
|
command.Dir = workDir
|
|
command.Stdout = config.Stdout
|
|
command.Stderr = config.Stderr
|
|
runnerErr := command.Run()
|
|
shutdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
shutdownErr := server.Shutdown(shutdownContext)
|
|
cancel()
|
|
serverErr := <-serverErrors
|
|
if errors.Is(serverErr, http.ErrServerClosed) {
|
|
serverErr = nil
|
|
}
|
|
return errors.Join(runnerErr, shutdownErr, serverErr)
|
|
}
|
|
|
|
// ExecutorConfigFromEnvironment reads the non-secret image configuration and
|
|
// the assignment-scoped values injected by the backend. The Workload API
|
|
// address follows SPIFFE_ENDPOINT_SOCKET through go-spiffe when not set here.
|
|
func ExecutorConfigFromEnvironment() (ExecutorConfig, error) {
|
|
backend := taskassignment.Backend(os.Getenv(EnvBackend))
|
|
if backend != taskassignment.BackendPod && backend != taskassignment.BackendVM {
|
|
return ExecutorConfig{}, fmt.Errorf("invalid %s %q", EnvBackend, backend)
|
|
}
|
|
config := ExecutorConfig{
|
|
AssignmentID: os.Getenv(EnvAssignmentID), Capability: os.Getenv(EnvCapability),
|
|
Backend: backend, FacadeURL: os.Getenv(EnvFacadeURL), FacadeSPIFFEID: os.Getenv(EnvFacadeID),
|
|
RunnerBinary: os.Getenv("GITEA_RUNNER_BINARY"), ListenAddress: "127.0.0.1:0",
|
|
Stdout: os.Stdout, Stderr: os.Stderr,
|
|
}
|
|
if config.AssignmentID == "" || config.Capability == "" || config.FacadeURL == "" || config.FacadeSPIFFEID == "" {
|
|
return ExecutorConfig{}, errors.New("complete runner assignment and facade environment is required")
|
|
}
|
|
return config, nil
|
|
}
|