feat: bootstrap official runner through SPIFFE facade
This commit is contained in:
@@ -34,11 +34,26 @@ type Metadata struct {
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
// LaunchSpec contains the durable resource metadata and the short-lived
|
||||
// executor environment. Environment values configure the one-shot runner but
|
||||
// are deliberately excluded from labels and annotations.
|
||||
type LaunchSpec struct {
|
||||
Metadata Metadata
|
||||
Environment map[string]string
|
||||
}
|
||||
|
||||
// Bootstrap produces assignment-scoped executor configuration. Implementations
|
||||
// must be deterministic so a redelivery after controller restart creates the
|
||||
// same credentials without storing another lifecycle record.
|
||||
type Bootstrap interface {
|
||||
Environment(taskassignment.Assignment) (map[string]string, error)
|
||||
}
|
||||
|
||||
// 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, taskassignment.Assignment, Metadata) (*Executor, error)
|
||||
Create(context.Context, taskassignment.Assignment, LaunchSpec) (*Executor, error)
|
||||
BindIdentity(context.Context, *Executor, taskidentity.Identity) error
|
||||
Delete(context.Context, *Executor) error
|
||||
}
|
||||
@@ -53,16 +68,17 @@ type TaskState interface {
|
||||
// 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
|
||||
Backend Backend
|
||||
Tasks TaskState
|
||||
Bootstrap Bootstrap
|
||||
}
|
||||
|
||||
// Accept completes the durable handoff from JetStream to the backend. Once it
|
||||
// returns true, all recovery information exists in Kubernetes/OpenSandbox and
|
||||
// the assignment message can be acknowledged immediately.
|
||||
func (w Worker) Accept(ctx context.Context, assignment taskassignment.Assignment) (bool, error) {
|
||||
if w.Backend == nil || w.Tasks == nil {
|
||||
return false, errors.New("backend and Gitea task state are required")
|
||||
if w.Backend == nil || w.Tasks == nil || w.Bootstrap == nil {
|
||||
return false, errors.New("backend, Gitea task state, and runner bootstrap are required")
|
||||
}
|
||||
if assignment.ID == "" || assignment.Task == nil {
|
||||
return false, errors.New("valid assignment is required")
|
||||
@@ -84,7 +100,11 @@ func (w Worker) Accept(ctx context.Context, assignment taskassignment.Assignment
|
||||
return true, nil
|
||||
}
|
||||
if executor == nil {
|
||||
executor, err = w.Backend.Create(ctx, assignment, BackendMetadata(assignment))
|
||||
launch, launchErr := w.launchSpec(assignment)
|
||||
if launchErr != nil {
|
||||
return false, launchErr
|
||||
}
|
||||
executor, err = w.Backend.Create(ctx, assignment, launch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -101,8 +121,8 @@ func (w Worker) Accept(ctx context.Context, assignment taskassignment.Assignment
|
||||
// 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 taskassignment.Assignment) (done bool, err error) {
|
||||
if w.Backend == nil || w.Tasks == nil {
|
||||
return false, errors.New("backend and Gitea task state are required")
|
||||
if w.Backend == nil || w.Tasks == nil || w.Bootstrap == nil {
|
||||
return false, errors.New("backend, Gitea task state, and runner bootstrap are required")
|
||||
}
|
||||
if assignment.ID == "" || assignment.Task == nil {
|
||||
return false, errors.New("valid assignment is required")
|
||||
@@ -126,7 +146,11 @@ func (w Worker) Handle(ctx context.Context, assignment taskassignment.Assignment
|
||||
}
|
||||
|
||||
if executor == nil {
|
||||
executor, err = w.Backend.Create(ctx, assignment, BackendMetadata(assignment))
|
||||
launch, launchErr := w.launchSpec(assignment)
|
||||
if launchErr != nil {
|
||||
return false, launchErr
|
||||
}
|
||||
executor, err = w.Backend.Create(ctx, assignment, launch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -154,6 +178,14 @@ func (w Worker) Handle(ctx context.Context, assignment taskassignment.Assignment
|
||||
}
|
||||
}
|
||||
|
||||
func (w Worker) launchSpec(assignment taskassignment.Assignment) (LaunchSpec, error) {
|
||||
environment, err := w.Bootstrap.Environment(assignment)
|
||||
if err != nil {
|
||||
return LaunchSpec{}, err
|
||||
}
|
||||
return LaunchSpec{Metadata: BackendMetadata(assignment), Environment: environment}, nil
|
||||
}
|
||||
|
||||
// BackendMetadata is the shared metadata contract for Pods and OpenSandbox.
|
||||
func BackendMetadata(assignment taskassignment.Assignment) Metadata {
|
||||
return Metadata{
|
||||
|
||||
@@ -17,8 +17,14 @@ type fakeBackend struct {
|
||||
deleted int
|
||||
}
|
||||
|
||||
type fakeBootstrap struct{}
|
||||
|
||||
func (fakeBootstrap) Environment(taskassignment.Assignment) (map[string]string, error) {
|
||||
return map[string]string{"CI_RUNNER_CAPABILITY": "capability"}, nil
|
||||
}
|
||||
|
||||
func (b *fakeBackend) Find(context.Context, string) (*Executor, error) { return b.executor, nil }
|
||||
func (b *fakeBackend) Create(_ context.Context, _ taskassignment.Assignment, _ Metadata) (*Executor, error) {
|
||||
func (b *fakeBackend) Create(_ context.Context, _ taskassignment.Assignment, _ LaunchSpec) (*Executor, error) {
|
||||
b.created++
|
||||
b.executor = &Executor{Name: "executor", IdentityTarget: "pod-uid", Phase: PhaseRunning}
|
||||
return b.executor, nil
|
||||
@@ -60,7 +66,7 @@ func assignment() taskassignment.Assignment {
|
||||
|
||||
func TestHandleRecoversExistingExecutorWithoutCreatingAnother(t *testing.T) {
|
||||
backend := &fakeBackend{executor: &Executor{Name: "existing", IdentityTarget: "uid", Phase: PhaseRunning}}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}, Bootstrap: fakeBootstrap{}}
|
||||
|
||||
done, err := worker.Handle(context.Background(), assignment())
|
||||
if err != nil || done {
|
||||
@@ -73,7 +79,7 @@ func TestHandleRecoversExistingExecutorWithoutCreatingAnother(t *testing.T) {
|
||||
|
||||
func TestAcceptAcknowledgesAfterBackendAndIdentityAreDurable(t *testing.T) {
|
||||
backend := &fakeBackend{}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}, Bootstrap: fakeBootstrap{}}
|
||||
|
||||
accepted, err := worker.Accept(context.Background(), assignment())
|
||||
if err != nil || !accepted {
|
||||
@@ -86,7 +92,7 @@ func TestAcceptAcknowledgesAfterBackendAndIdentityAreDurable(t *testing.T) {
|
||||
|
||||
func TestAcceptRetriesWhileBackendIdentityTargetIsUnavailable(t *testing.T) {
|
||||
backend := &fakeBackend{executor: &Executor{Name: "pending", Phase: PhasePending}}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}}
|
||||
worker := Worker{Backend: backend, Tasks: &fakeTasks{}, Bootstrap: fakeBootstrap{}}
|
||||
|
||||
accepted, err := worker.Accept(context.Background(), assignment())
|
||||
if err != nil || accepted {
|
||||
@@ -100,7 +106,7 @@ func TestAcceptRetriesWhileBackendIdentityTargetIsUnavailable(t *testing.T) {
|
||||
func TestHandleReportsBeforeCleanupAndBecomesRecoverable(t *testing.T) {
|
||||
backend := &fakeBackend{executor: &Executor{Name: "finished", IdentityTarget: "uid", Phase: PhaseSucceeded}}
|
||||
tasks := &fakeTasks{}
|
||||
worker := Worker{Backend: backend, Tasks: tasks}
|
||||
worker := Worker{Backend: backend, Tasks: tasks, Bootstrap: fakeBootstrap{}}
|
||||
|
||||
done, err := worker.Handle(context.Background(), assignment())
|
||||
if err != nil || !done {
|
||||
|
||||
Reference in New Issue
Block a user