212 lines
6.0 KiB
Go
212 lines
6.0 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
agentctl "github.com/kandev/kandev/internal/agent/runtime/agentctl"
|
|
agentctltypes "github.com/kandev/kandev/internal/agentctl/types"
|
|
)
|
|
|
|
type StartProcessRequest struct {
|
|
SessionID string
|
|
Kind string
|
|
ScriptName string
|
|
Command string
|
|
WorkingDir string
|
|
Env map[string]string
|
|
}
|
|
|
|
func (m *Manager) StartProcess(ctx context.Context, req StartProcessRequest) (*agentctl.ProcessInfo, error) {
|
|
if req.SessionID == "" {
|
|
return nil, fmt.Errorf("session_id is required")
|
|
}
|
|
execution, ok := m.executionStore.GetBySessionID(req.SessionID)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", ErrNoExecutionForSession, req.SessionID)
|
|
}
|
|
client, releaseClient := execution.AcquireAgentCtlClient()
|
|
defer releaseClient()
|
|
if client == nil {
|
|
return nil, fmt.Errorf("agentctl client not available for session %s", req.SessionID)
|
|
}
|
|
|
|
lease, err := m.acquireActivity(ctx, processActivityKind(req.Kind))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
startCtx, cancelStart := context.WithTimeout(context.WithoutCancel(ctx), time.Minute)
|
|
defer cancelStart()
|
|
process, err := client.StartProcess(startCtx, agentctl.StartProcessRequest{
|
|
SessionID: req.SessionID,
|
|
Kind: agentctl.ProcessKind(req.Kind),
|
|
ScriptName: req.ScriptName,
|
|
Command: req.Command,
|
|
WorkingDir: req.WorkingDir,
|
|
Env: processEnvironment(execution, req.Env),
|
|
})
|
|
if err != nil {
|
|
lease.Release()
|
|
return nil, err
|
|
}
|
|
if isTerminalProcessStatus(process.Status) {
|
|
lease.Release()
|
|
} else {
|
|
m.trackActivity(processActivityKey(process.ID), lease)
|
|
}
|
|
return process, nil
|
|
}
|
|
|
|
// WaitForAgentctlReadyForSession waits for agentctl to be ready for a session.
|
|
func (m *Manager) WaitForAgentctlReadyForSession(ctx context.Context, sessionID string) error {
|
|
if sessionID == "" {
|
|
return fmt.Errorf("session_id is required")
|
|
}
|
|
execution, ok := m.executionStore.GetBySessionID(sessionID)
|
|
if !ok {
|
|
return fmt.Errorf("%w: %s", ErrNoExecutionForSession, sessionID)
|
|
}
|
|
client, releaseClient := execution.AcquireAgentCtlClient()
|
|
defer releaseClient()
|
|
if client == nil {
|
|
return fmt.Errorf("agentctl client not available for session %s", sessionID)
|
|
}
|
|
return client.WaitForReady(ctx, 10*time.Second)
|
|
}
|
|
|
|
func (m *Manager) StopProcess(ctx context.Context, processID string) error {
|
|
if processID == "" {
|
|
return fmt.Errorf("process_id is required")
|
|
}
|
|
for _, exec := range m.executionStore.List() {
|
|
client, releaseClient := exec.AcquireAgentCtlClient()
|
|
if client == nil {
|
|
releaseClient()
|
|
continue
|
|
}
|
|
if _, err := client.GetProcess(ctx, processID, false); err != nil {
|
|
releaseClient()
|
|
continue
|
|
}
|
|
if err := client.StopProcess(ctx, processID); err != nil {
|
|
releaseClient()
|
|
return err
|
|
}
|
|
releaseClient()
|
|
m.releaseActivity(processActivityKey(processID))
|
|
return nil
|
|
}
|
|
return fmt.Errorf("process not found: %s", processID)
|
|
}
|
|
|
|
func processEnvironment(execution *AgentExecution, requested map[string]string) map[string]string {
|
|
managedPath := execution.metadataString(managedGoCacheMetadataKey)
|
|
if managedPath == "" {
|
|
return requested
|
|
}
|
|
env := make(map[string]string, len(requested)+1)
|
|
for key, value := range requested {
|
|
env[key] = value
|
|
}
|
|
env["GOCACHE"] = managedPath
|
|
return env
|
|
}
|
|
|
|
func isTerminalProcessStatus(status agentctl.ProcessStatus) bool {
|
|
switch status {
|
|
case agentctltypes.ProcessStatusExited, agentctltypes.ProcessStatusFailed, agentctltypes.ProcessStatusStopped:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// StopProcessForSession stops a running process by ID within a specific session.
|
|
func (m *Manager) StopProcessForSession(ctx context.Context, sessionID, processID string) error {
|
|
if sessionID == "" {
|
|
return fmt.Errorf("session_id is required")
|
|
}
|
|
if processID == "" {
|
|
return fmt.Errorf("process_id is required")
|
|
}
|
|
execution, ok := m.executionStore.GetBySessionID(sessionID)
|
|
if !ok {
|
|
return fmt.Errorf("%w: %s", ErrNoExecutionForSession, sessionID)
|
|
}
|
|
client, releaseClient := execution.AcquireAgentCtlClient()
|
|
defer releaseClient()
|
|
if client == nil {
|
|
return fmt.Errorf("agentctl client not available for session %s", sessionID)
|
|
}
|
|
if err := client.StopProcess(ctx, processID); err != nil {
|
|
return err
|
|
}
|
|
m.releaseActivity(processActivityKey(processID))
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) ListProcesses(ctx context.Context, sessionID string) ([]agentctl.ProcessInfo, error) {
|
|
if sessionID == "" {
|
|
return nil, fmt.Errorf("session_id is required")
|
|
}
|
|
execution, ok := m.executionStore.GetBySessionID(sessionID)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", ErrNoExecutionForSession, sessionID)
|
|
}
|
|
client, releaseClient := execution.AcquireAgentCtlClient()
|
|
defer releaseClient()
|
|
if client == nil {
|
|
return nil, fmt.Errorf("agentctl client not available for session %s", sessionID)
|
|
}
|
|
return client.ListProcesses(ctx, sessionID)
|
|
}
|
|
|
|
func (m *Manager) GetProcess(ctx context.Context, processID string, includeOutput bool) (*agentctl.ProcessInfo, error) {
|
|
if processID == "" {
|
|
return nil, fmt.Errorf("process_id is required")
|
|
}
|
|
for _, exec := range m.executionStore.List() {
|
|
client, releaseClient := exec.AcquireAgentCtlClient()
|
|
if client == nil {
|
|
releaseClient()
|
|
continue
|
|
}
|
|
proc, err := client.GetProcess(ctx, processID, includeOutput)
|
|
releaseClient()
|
|
if err == nil {
|
|
return proc, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("process not found: %s", processID)
|
|
}
|
|
|
|
// StopAllProcesses attempts to stop all running processes across all executions.
|
|
func (m *Manager) StopAllProcesses(ctx context.Context) error {
|
|
executions := m.executionStore.List()
|
|
var errs []error
|
|
for _, exec := range executions {
|
|
client, releaseClient := exec.AcquireAgentCtlClient()
|
|
if client == nil {
|
|
releaseClient()
|
|
continue
|
|
}
|
|
procs, err := client.ListProcesses(ctx, exec.SessionID)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
releaseClient()
|
|
continue
|
|
}
|
|
for _, proc := range procs {
|
|
if err := client.StopProcess(ctx, proc.ID); err != nil {
|
|
errs = append(errs, err)
|
|
} else {
|
|
m.releaseActivity(processActivityKey(proc.ID))
|
|
}
|
|
}
|
|
releaseClient()
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|