建立无状态 worker reconcile 边界
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// Package taskworker reconciles assigned Gitea tasks against an executor backend.
|
||||
package taskworker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"git.ddupan.top/panxiao81/gitea-dynamic-runner/internal/taskidentity"
|
||||
"git.ddupan.top/panxiao81/gitea-dynamic-runner/internal/taskscheduler"
|
||||
)
|
||||
|
||||
// Phase is observed from Kubernetes or OpenSandbox, never stored by the worker.
|
||||
type Phase string
|
||||
|
||||
const (
|
||||
PhasePending Phase = "pending"
|
||||
PhaseRunning Phase = "running"
|
||||
PhaseSucceeded Phase = "succeeded"
|
||||
PhaseFailed Phase = "failed"
|
||||
)
|
||||
|
||||
// Executor is the backend resource discovered by assignment ID.
|
||||
type Executor struct {
|
||||
Name string
|
||||
IdentityTarget string
|
||||
Phase Phase
|
||||
}
|
||||
|
||||
// Metadata is persisted on the backend resource. Labels remain query-safe;
|
||||
// annotations retain complete identity context.
|
||||
type Metadata struct {
|
||||
Labels map[string]string
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
// Backend is implemented by the native Pod and OpenSandbox adapters.
|
||||
// Every method must be idempotent.
|
||||
type Backend interface {
|
||||
Find(context.Context, string) (*Executor, error)
|
||||
Create(context.Context, taskscheduler.Assignment, Metadata) (*Executor, error)
|
||||
BindIdentity(context.Context, *Executor, taskidentity.Identity) error
|
||||
Delete(context.Context, *Executor) error
|
||||
}
|
||||
|
||||
// TaskState uses Gitea as the authority for whether an assigned task has
|
||||
// already reached a terminal state.
|
||||
type TaskState interface {
|
||||
Terminal(context.Context, int64) (bool, error)
|
||||
Report(context.Context, int64, Phase) error
|
||||
}
|
||||
|
||||
// Worker has no correctness-critical in-memory state. Handle may be called
|
||||
// again for the same assignment after any operation.
|
||||
type Worker struct {
|
||||
Backend Backend
|
||||
Tasks TaskState
|
||||
}
|
||||
|
||||
// Handle performs one reconciliation. Done means the queue message may be
|
||||
// acknowledged. A false result should remain pending and be reconciled again.
|
||||
func (w Worker) Handle(ctx context.Context, assignment taskscheduler.Assignment) (done bool, err error) {
|
||||
if w.Backend == nil || w.Tasks == nil {
|
||||
return false, errors.New("backend and Gitea task state are required")
|
||||
}
|
||||
if assignment.ID == "" || assignment.Task == nil {
|
||||
return false, errors.New("valid assignment is required")
|
||||
}
|
||||
|
||||
terminal, err := w.Tasks.Terminal(ctx, assignment.Task.GetId())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
executor, err := w.Backend.Find(ctx, assignment.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if terminal {
|
||||
if executor != nil {
|
||||
if err := w.Backend.Delete(ctx, executor); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if executor == nil {
|
||||
executor, err = w.Backend.Create(ctx, assignment, BackendMetadata(assignment))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if executor.IdentityTarget == "" {
|
||||
return false, nil
|
||||
}
|
||||
if err := w.Backend.BindIdentity(ctx, executor, assignment.Identity); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch executor.Phase {
|
||||
case PhaseSucceeded, PhaseFailed:
|
||||
// Reporting first closes the delete-before-ack crash window: after a
|
||||
// restart Gitea prevents this assignment from executing a second time.
|
||||
if err := w.Tasks.Report(ctx, assignment.Task.GetId(), executor.Phase); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := w.Backend.Delete(ctx, executor); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BackendMetadata is the shared metadata contract for Pods and OpenSandbox.
|
||||
func BackendMetadata(assignment taskscheduler.Assignment) Metadata {
|
||||
return Metadata{
|
||||
Labels: map[string]string{
|
||||
"ci.ddupan.top/assignment-id": assignment.ID,
|
||||
"ci.ddupan.top/task-id": strconv.FormatInt(assignment.Task.GetId(), 10),
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"ci.ddupan.top/repository": assignment.Identity.Repository,
|
||||
"ci.ddupan.top/job-key": assignment.Identity.Task,
|
||||
"ci.ddupan.top/spiffe-id": assignment.Identity.SPIFFEID,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user