chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
package workspaceruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/app/lanegit"
|
||||
"inbox/internal/app/runtimecodex"
|
||||
"inbox/internal/app/runtimeconfig"
|
||||
"inbox/internal/base/slug"
|
||||
"inbox/internal/base/timeutil"
|
||||
"inbox/internal/domain/lane"
|
||||
"inbox/internal/domain/role"
|
||||
"inbox/internal/domain/skill"
|
||||
"inbox/internal/domain/workspace"
|
||||
)
|
||||
|
||||
type store interface {
|
||||
GetProject(context.Context, string) (workspace.Project, error)
|
||||
UpdateProjectDefaultBranch(context.Context, string, string) error
|
||||
GetWorkspace(context.Context, string) (workspace.Workspace, error)
|
||||
UpdateWorkspace(context.Context, workspace.Workspace) error
|
||||
GetLane(context.Context, string) (lane.Record, error)
|
||||
UpdateLane(context.Context, lane.Record) (lane.Record, error)
|
||||
}
|
||||
|
||||
type runtimeCodexStore interface {
|
||||
store
|
||||
ListRoles(context.Context) ([]role.Definition, error)
|
||||
GetRole(context.Context, string) (role.Definition, error)
|
||||
GetRoleConfig(context.Context, string) (role.Config, error)
|
||||
ListRolePrompts(context.Context, string) ([]role.Prompt, error)
|
||||
ListRoleSkillBindings(context.Context, string) ([]role.SkillBinding, error)
|
||||
ListSkillsByIDs(context.Context, []string) (map[string]skill.Definition, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
store store
|
||||
projectRoot string
|
||||
clock timeutil.Clock
|
||||
workspacesDir string
|
||||
git *gitWorktreeManager
|
||||
worker *hostLaneWorkerBinary
|
||||
inbox *hostInboxBinary
|
||||
codexHomes *runtimecodex.Materializer
|
||||
runtime *containerRuntime
|
||||
}
|
||||
|
||||
type Runtime struct {
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerState string `json:"container_state"`
|
||||
WorktreePath string `json:"worktree_path"`
|
||||
RunnerEndpoint string `json:"runner_endpoint"`
|
||||
Ensured bool `json:"ensured"`
|
||||
}
|
||||
|
||||
func NewService(store store, projectRoot, workspacesDir string, serverPort int, clock timeutil.Clock, runner lanegit.Runner) *Service {
|
||||
if clock == nil {
|
||||
clock = timeutil.SystemClock{}
|
||||
}
|
||||
if runner == nil {
|
||||
runner = lanegit.ExecRunner{}
|
||||
}
|
||||
root := strings.TrimSpace(projectRoot)
|
||||
probe := newEndpointProbe()
|
||||
var codexHomes *runtimecodex.Materializer
|
||||
if configStore, ok := store.(runtimeCodexStore); ok {
|
||||
codexHomes = runtimecodex.NewMaterializer(
|
||||
configStore,
|
||||
runtimeconfig.NewService(configStore, configStore, clock),
|
||||
)
|
||||
}
|
||||
return &Service{
|
||||
store: store,
|
||||
projectRoot: root,
|
||||
clock: clock,
|
||||
workspacesDir: strings.TrimSpace(workspacesDir),
|
||||
git: &gitWorktreeManager{projectRoot: root, runner: runner},
|
||||
worker: &hostLaneWorkerBinary{projectRoot: root, runner: runner},
|
||||
inbox: &hostInboxBinary{projectRoot: root, runner: runner},
|
||||
codexHomes: codexHomes,
|
||||
runtime: &containerRuntime{projectRoot: root, serverPort: serverPort, runner: runner, probe: probe},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) EnsureRepository(ctx context.Context, projectDir string) (string, error) {
|
||||
return s.git.ensureRepository(ctx, projectDir)
|
||||
}
|
||||
|
||||
func (s *Service) Ensure(ctx context.Context, workspaceID string) (workspace.Workspace, Runtime, error) {
|
||||
ws, err := s.store.GetWorkspace(ctx, strings.TrimSpace(workspaceID))
|
||||
if err != nil {
|
||||
return workspace.Workspace{}, Runtime{}, err
|
||||
}
|
||||
project, err := s.store.GetProject(ctx, ws.ProjectID)
|
||||
if err != nil {
|
||||
return workspace.Workspace{}, Runtime{}, err
|
||||
}
|
||||
baseBranch, err := s.git.ensureRepository(ctx, project.RootPath)
|
||||
if err != nil {
|
||||
_, _ = s.failWorkspace(ctx, ws, err)
|
||||
return ws, Runtime{}, err
|
||||
}
|
||||
if project.DefaultBranch != baseBranch {
|
||||
if err := s.store.UpdateProjectDefaultBranch(ctx, project.ID, baseBranch); err != nil {
|
||||
return ws, Runtime{}, err
|
||||
}
|
||||
project.DefaultBranch = baseBranch
|
||||
}
|
||||
if strings.TrimSpace(s.workspacesDir) != "" {
|
||||
ws.RootPath = filepath.Join(s.workspacesDir, ws.Slug)
|
||||
}
|
||||
ws = workspace.ApplyManagedRuntimeConfig(ws, s.workspacesDir, baseBranch)
|
||||
return s.ensureWorkspaceHost(ctx, project, ws)
|
||||
}
|
||||
|
||||
func (s *Service) ensureWorkspaceHost(ctx context.Context, project workspace.Project, ws workspace.Workspace) (workspace.Workspace, Runtime, error) {
|
||||
runtime := Runtime{
|
||||
ContainerName: "",
|
||||
WorktreePath: ws.RootPath,
|
||||
}
|
||||
if err := s.git.ensureWorktree(ctx, project.RootPath, ws.RootPath, ws.BaseBranch, ws.WorktreeBranch); err != nil {
|
||||
failed, _ := s.failWorkspace(ctx, ws, err)
|
||||
return failed, runtime, err
|
||||
}
|
||||
now := timeutil.FormatRFC3339(s.clock.Now())
|
||||
ws.RootPath = filepath.Clean(ws.RootPath)
|
||||
ws.RuntimeBackend = "host"
|
||||
ws.ProvisionState = "ready"
|
||||
ws.ProvisionError = ""
|
||||
ws.LastProvisionedAt = now
|
||||
ws.ContainerState = ""
|
||||
if err := s.store.UpdateWorkspace(ctx, ws); err != nil {
|
||||
return ws, runtime, err
|
||||
}
|
||||
runtime.ContainerState = ""
|
||||
runtime.RunnerEndpoint = ""
|
||||
runtime.Ensured = true
|
||||
return ws, runtime, nil
|
||||
}
|
||||
|
||||
func (s *Service) failWorkspace(ctx context.Context, ws workspace.Workspace, cause error) (workspace.Workspace, error) {
|
||||
ws.ProvisionState = "failed"
|
||||
ws.ProvisionError = strings.TrimSpace(cause.Error())
|
||||
ws.ContainerState = "missing"
|
||||
if err := s.store.UpdateWorkspace(ctx, ws); err != nil {
|
||||
return ws, err
|
||||
}
|
||||
return ws, cause
|
||||
}
|
||||
|
||||
func (s *Service) EnsureLane(ctx context.Context, laneID string) (lane.Record, error) {
|
||||
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
ws, err := s.store.GetWorkspace(ctx, item.WorkspaceID)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
project, err := s.store.GetProject(ctx, ws.ProjectID)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
baseBranch, err := s.git.ensureRepository(ctx, project.RootPath)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
if ws, _, err = s.ensureWorkspaceHost(ctx, project, ws); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
if item.BaseBranch == "" {
|
||||
item.BaseBranch = firstNonEmpty(strings.TrimSpace(ws.BaseBranch), baseBranch)
|
||||
}
|
||||
if item.Slug == "" {
|
||||
item.Slug = slug.Normalize(item.Name)
|
||||
}
|
||||
if item.BranchName == "" {
|
||||
item.BranchName = lane.DefaultBranchName(ws.Slug, item.TopicID, item.Slug)
|
||||
}
|
||||
if item.WorktreePath == "" {
|
||||
item.WorktreePath = lane.DefaultWorktreePath(ws.RootPath, ws.Slug, item.TopicID, item.Slug)
|
||||
}
|
||||
if item.ContainerName == "" {
|
||||
item.ContainerName = lane.DefaultContainerName(ws.Slug, item.TopicID, item.Slug)
|
||||
}
|
||||
if err := s.git.ensureWorktree(ctx, project.RootPath, item.WorktreePath, item.BaseBranch, item.BranchName); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
workerCodexDir := ""
|
||||
if s.codexHomes != nil {
|
||||
runtimeRoot := runtimecodex.HostContainerRuntimeRoot(s.projectRoot, ws.ID)
|
||||
if err := s.codexHomes.Sync(ctx, ws.ID, runtimeRoot); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
workerCodexDir = runtimecodex.HostContainerCodexDir(s.projectRoot, ws.ID, "worker")
|
||||
}
|
||||
workerBinary, err := s.worker.ensure(ctx)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
inboxBinary, err := s.inbox.ensure(ctx)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
if err := s.runtime.ensureRunnerImage(ctx); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
endpoint, err := s.runtime.ensureLaneContainer(ctx, ws, item, workerBinary, inboxBinary, workerCodexDir)
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
item.RuntimeEndpoint = endpoint
|
||||
item.Status = lane.StatusRunning
|
||||
now := timeutil.FormatRFC3339(s.clock.Now())
|
||||
if item.StartedAt == "" {
|
||||
item.StartedAt = now
|
||||
}
|
||||
item.UpdatedAt = now
|
||||
return s.store.UpdateLane(ctx, item)
|
||||
}
|
||||
|
||||
func (s *Service) StopLane(ctx context.Context, laneID string) (lane.Record, error) {
|
||||
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
if item.ContainerName != "" {
|
||||
if err := s.runtime.stopContainer(ctx, item.ContainerName); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
}
|
||||
item.Status = lane.StatusCancelled
|
||||
item.CompletedAt = timeutil.FormatRFC3339(s.clock.Now())
|
||||
item.RuntimeEndpoint = ""
|
||||
return s.store.UpdateLane(ctx, item)
|
||||
}
|
||||
|
||||
func (s *Service) ReleaseLaneRuntime(ctx context.Context, laneID string) (lane.Record, error) {
|
||||
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
|
||||
if err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
if item.ContainerName != "" {
|
||||
if err := s.runtime.stopContainer(ctx, item.ContainerName); err != nil {
|
||||
return lane.Record{}, err
|
||||
}
|
||||
}
|
||||
item.RuntimeEndpoint = ""
|
||||
item.UpdatedAt = timeutil.FormatRFC3339(s.clock.Now())
|
||||
return s.store.UpdateLane(ctx, item)
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user