建立 Go Task Scheduler 协议骨架
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
// Package giteaactions provides the authenticated Gitea RunnerService client.
|
||||
package giteaactions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"gitea.dev/actionslib/pkg/protocol"
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
"gitea.dev/actionslib/runner/v1/runnerv1connect"
|
||||
)
|
||||
|
||||
// Client is the subset of RunnerService owned by the scheduler.
|
||||
type Client struct {
|
||||
runner runnerv1connect.RunnerServiceClient
|
||||
}
|
||||
|
||||
// NewClient authenticates every RPC with the persistent scheduler runner.
|
||||
func NewClient(httpClient connect.HTTPClient, instanceURL, uuid, token string) *Client {
|
||||
auth := connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
|
||||
return func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) {
|
||||
request.Header().Set("User-Agent", "gitea-dynamic-runner-go/0")
|
||||
request.Header().Set(protocol.UUIDHeader, uuid)
|
||||
request.Header().Set(protocol.TokenHeader, token)
|
||||
return next(ctx, request)
|
||||
}
|
||||
})
|
||||
baseURL := strings.TrimRight(instanceURL, "/") + "/api/actions"
|
||||
return &Client{runner: runnerv1connect.NewRunnerServiceClient(
|
||||
httpClient,
|
||||
baseURL,
|
||||
connect.WithInterceptors(auth),
|
||||
)}
|
||||
}
|
||||
|
||||
// Declare advertises the scheduler labels before tasks are fetched.
|
||||
func (c *Client) Declare(ctx context.Context, version string, labels []string) error {
|
||||
_, err := c.runner.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
|
||||
Version: version,
|
||||
Labels: labels,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchTask asks Gitea to atomically assign the next matching task.
|
||||
func (c *Client) FetchTask(ctx context.Context, tasksVersion int64) (*runnerv1.FetchTaskResponse, error) {
|
||||
response, err := c.runner.FetchTask(ctx, connect.NewRequest(&runnerv1.FetchTaskRequest{
|
||||
TasksVersion: tasksVersion,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Msg, nil
|
||||
}
|
||||
|
||||
// DefaultHTTPClient is suitable for the scheduler's long-lived connection.
|
||||
func DefaultHTTPClient() *http.Client {
|
||||
return &http.Client{Transport: http.DefaultTransport}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package giteaactions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"gitea.dev/actionslib/pkg/protocol"
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
"gitea.dev/actionslib/runner/v1/runnerv1connect"
|
||||
)
|
||||
|
||||
type runnerService struct {
|
||||
runnerv1connect.UnimplementedRunnerServiceHandler
|
||||
t *testing.T
|
||||
declaredLabels []string
|
||||
fetchedVersion int64
|
||||
expectedUUID string
|
||||
expectedToken string
|
||||
}
|
||||
|
||||
func (s *runnerService) checkAuth(request connect.AnyRequest) {
|
||||
s.t.Helper()
|
||||
if got := request.Header().Get(protocol.UUIDHeader); got != s.expectedUUID {
|
||||
s.t.Errorf("runner UUID header = %q", got)
|
||||
}
|
||||
if got := request.Header().Get(protocol.TokenHeader); got != s.expectedToken {
|
||||
s.t.Errorf("runner token header = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *runnerService) Declare(_ context.Context, request *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) {
|
||||
s.checkAuth(request)
|
||||
s.declaredLabels = request.Msg.Labels
|
||||
return connect.NewResponse(&runnerv1.DeclareResponse{}), nil
|
||||
}
|
||||
|
||||
func (s *runnerService) FetchTask(_ context.Context, request *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) {
|
||||
s.checkAuth(request)
|
||||
s.fetchedVersion = request.Msg.TasksVersion
|
||||
return connect.NewResponse(&runnerv1.FetchTaskResponse{
|
||||
Task: &runnerv1.Task{Id: 42},
|
||||
TasksVersion: 8,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func TestClientUsesOfficialRunnerProtocol(t *testing.T) {
|
||||
service := &runnerService{
|
||||
t: t,
|
||||
expectedUUID: "runner-uuid",
|
||||
expectedToken: "runner-token",
|
||||
}
|
||||
path, handler := runnerv1connect.NewRunnerServiceHandler(service)
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/api/actions"+path, http.StripPrefix("/api/actions", handler))
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.Client(), server.URL, service.expectedUUID, service.expectedToken)
|
||||
labels := []string{"self-hosted", "pod", "vm"}
|
||||
if err := client.Declare(context.Background(), "0.1.0", labels); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := client.FetchTask(context.Background(), 7)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(service.declaredLabels, labels) {
|
||||
t.Fatalf("declared labels = %#v", service.declaredLabels)
|
||||
}
|
||||
if service.fetchedVersion != 7 || response.GetTasksVersion() != 8 || response.GetTask().GetId() != 42 {
|
||||
t.Fatalf("unexpected FetchTask exchange: request=%d response=%v", service.fetchedVersion, response)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user