From acde2c76239d93d2c138af6cfac25ea5991bccd7 Mon Sep 17 00:00:00 2001 From: mrhid6 Date: Mon, 20 Jul 2026 17:35:58 +0100 Subject: [PATCH] feat: More verbose logging on workflow logs --- internal/exec/exec.go | 25 +++++++++++++++++++++++++ internal/grpc/pb/vantage.pb.go | 22 ++++++++++++++++------ internal/sync/sync.go | 13 +++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 72f59e7..44bc506 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -35,10 +35,21 @@ func (w *streamWriter) Write(p []byte) (int, error) { return len(p), nil } +// WorkspacePath returns the per-run working directory for a workspace id. The +// same id always maps to the same path so RunStep and the cleanup command agree. +func WorkspacePath(workspaceID string) string { + return filepath.Join(os.TempDir(), "vantage-run-"+workspaceID) +} + // RunStep writes the script to a temp file, provides a WORKFLOW_ENV file for // the script to append KEY=value output to, executes it under the requested // interpreter, and streams output via emit, returning the terminal result // with empty stdout/stderr but populated exit_code/output_env. +// +// When the command carries a WorkspaceId the step runs with that per-run working +// directory as its cwd (created here if missing); the server removes it once the +// run finishes. The script and env files always live in a private temp dir so +// they never leak into the shared workspace. func RunStep(cmd *pb.RunStepCmd, emit func(seq uint64, data []byte)) *pb.StepResult { res := &pb.StepResult{CommandId: "", OutputEnv: map[string]string{}} @@ -50,6 +61,16 @@ func RunStep(cmd *pb.RunStepCmd, emit func(seq uint64, data []byte)) *pb.StepRes } defer os.RemoveAll(dir) + workDir := "" + if cmd.WorkspaceId != "" { + workDir = WorkspacePath(cmd.WorkspaceId) + if err := os.MkdirAll(workDir, 0700); err != nil { + res.ExitCode = 1 + res.Stderr = "create workspace: " + err.Error() + return res + } + } + envFile := filepath.Join(dir, "workflow_env") if err := os.WriteFile(envFile, nil, 0600); err != nil { res.ExitCode = 1 @@ -91,6 +112,10 @@ func RunStep(cmd *pb.RunStepCmd, emit func(seq uint64, data []byte)) *pb.StepRes c = exec.CommandContext(ctx, "bash", scriptPath) } + if workDir != "" { + c.Dir = workDir + } + c.Env = append(os.Environ(), "WORKFLOW_ENV="+envFile) for k, v := range cmd.Env { c.Env = append(c.Env, k+"="+v) diff --git a/internal/grpc/pb/vantage.pb.go b/internal/grpc/pb/vantage.pb.go index 6064354..c90e82d 100644 --- a/internal/grpc/pb/vantage.pb.go +++ b/internal/grpc/pb/vantage.pb.go @@ -63,12 +63,19 @@ type ReportUpdatesResponse struct{} type ApplyUpdatesCmd struct{} type ServerCommand struct { - CommandId string `json:"command_id"` - GenerateKey *GenerateKeyCmd `json:"generate_key,omitempty"` - DeleteKey *DeleteKeyCmd `json:"delete_key,omitempty"` - UpdateAgent *UpdateAgentCmd `json:"update_agent,omitempty"` - ApplyUpdates *ApplyUpdatesCmd `json:"apply_updates,omitempty"` - RunStep *RunStepCmd `json:"run_step,omitempty"` + CommandId string `json:"command_id"` + GenerateKey *GenerateKeyCmd `json:"generate_key,omitempty"` + DeleteKey *DeleteKeyCmd `json:"delete_key,omitempty"` + UpdateAgent *UpdateAgentCmd `json:"update_agent,omitempty"` + ApplyUpdates *ApplyUpdatesCmd `json:"apply_updates,omitempty"` + RunStep *RunStepCmd `json:"run_step,omitempty"` + CleanupWorkspace *CleanupWorkspaceCmd `json:"cleanup_workspace,omitempty"` +} + +// CleanupWorkspaceCmd tells the agent to recursively remove the run's working +// directory once all steps on that server have finished. +type CleanupWorkspaceCmd struct { + WorkspaceId string `json:"workspace_id"` } type DeleteKeyCmd struct { @@ -110,6 +117,9 @@ type RunStepCmd struct { Script string `json:"script"` Env map[string]string `json:"env,omitempty"` TimeoutSeconds int `json:"timeout_seconds,omitempty"` + // WorkspaceId names the per-run working directory the agent creates and uses + // as the step's cwd. Empty means run in the agent's default directory. + WorkspaceId string `json:"workspace_id,omitempty"` } type StepResult struct { diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 99a0430..5daac75 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -198,6 +198,9 @@ func connectAndHandleStream(ctx context.Context, cfg *config.Config) error { if cmd.ApplyUpdates != nil { go handleApplyUpdates(cfg, cmd) } + if cmd.CleanupWorkspace != nil { + go handleCleanupWorkspace(cmd) + } if cmd.RunStep != nil { go func(rc *pb.RunStepCmd, cid string) { emit := func(seq uint64, data []byte) { @@ -286,6 +289,16 @@ func handleApplyUpdates(cfg *config.Config, cmd *pb.ServerCommand) { _ = client.ReportUpdates(cfg.ServerID, cfg.AgentToken, nil) } +func handleCleanupWorkspace(cmd *pb.ServerCommand) { + id := cmd.CleanupWorkspace.WorkspaceId + dir := agentexec.WorkspacePath(id) + if err := os.RemoveAll(dir); err != nil { + log.Printf("cleanup workspace %s failed (cmd=%s): %v", dir, cmd.CommandId, err) + return + } + log.Printf("removed run workspace %s (cmd=%s)", dir, cmd.CommandId) +} + func handleDeleteKey(cmd *pb.ServerCommand) { label := cmd.DeleteKey.Label keyPath := fmt.Sprintf("/root/.ssh/vantage_%s", strings.ReplaceAll(label, " ", "_"))