feat(agent): execute RunStepCmd with WORKFLOW_ENV capture

This commit is contained in:
2026-07-20 11:29:14 +01:00
parent 1b286762f6
commit f0c86a3bdf
2 changed files with 129 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
package exec
import (
"bufio"
"bytes"
"context"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/mrhid6/vantage/agent/internal/grpc/pb"
)
// 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 returns captured output plus parsed output env.
func RunStep(cmd *pb.RunStepCmd) *pb.StepResult {
res := &pb.StepResult{CommandId: "", OutputEnv: map[string]string{}}
dir, err := os.MkdirTemp("", "vantage-step-")
if err != nil {
res.ExitCode = 1
res.Stderr = "create temp dir: " + err.Error()
return res
}
defer os.RemoveAll(dir)
envFile := filepath.Join(dir, "workflow_env")
if err := os.WriteFile(envFile, nil, 0600); err != nil {
res.ExitCode = 1
res.Stderr = "create env file: " + err.Error()
return res
}
var scriptPath string
var c *exec.Cmd
timeout := time.Duration(cmd.TimeoutSeconds) * time.Second
if timeout <= 0 {
timeout = 30 * time.Minute
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
switch cmd.Interpreter {
case "powershell":
scriptPath = filepath.Join(dir, "step.ps1")
if err := os.WriteFile(scriptPath, []byte(cmd.Script), 0600); err != nil {
res.ExitCode = 1
res.Stderr = err.Error()
return res
}
shell := "pwsh"
if runtime.GOOS == "windows" {
if _, err := exec.LookPath("pwsh"); err != nil {
shell = "powershell.exe"
}
}
c = exec.CommandContext(ctx, shell, "-NoProfile", "-NonInteractive", "-File", scriptPath)
default: // "bash"
scriptPath = filepath.Join(dir, "step.sh")
if err := os.WriteFile(scriptPath, []byte(cmd.Script), 0700); err != nil {
res.ExitCode = 1
res.Stderr = err.Error()
return res
}
c = exec.CommandContext(ctx, "bash", scriptPath)
}
c.Env = append(os.Environ(), "WORKFLOW_ENV="+envFile)
for k, v := range cmd.Env {
c.Env = append(c.Env, k+"="+v)
}
var stdout, stderr bytes.Buffer
c.Stdout = &stdout
c.Stderr = &stderr
runErr := c.Run()
res.Stdout = stdout.String()
res.Stderr = stderr.String()
if ctx.Err() == context.DeadlineExceeded {
res.ExitCode = 124
res.Stderr += "\n[vantage] step timed out"
} else if ee, ok := runErr.(*exec.ExitError); ok {
res.ExitCode = ee.ExitCode()
} else if runErr != nil {
res.ExitCode = 1
res.Stderr += "\n[vantage] " + runErr.Error()
}
res.OutputEnv = parseEnvFile(envFile)
return res
}
// parseEnvFile reads KEY=value lines (last write wins). Blank lines and lines
// without '=' are ignored.
func parseEnvFile(path string) map[string]string {
out := map[string]string{}
f, err := os.Open(path)
if err != nil {
return out
}
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for sc.Scan() {
line := sc.Text()
i := strings.IndexByte(line, '=')
if i <= 0 {
continue
}
out[line[:i]] = line[i+1:]
}
return out
}