1package executor
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "os"
8 "os/exec"
9
10 "github.com/bnema/archup/internal/domain/ports"
11)
12
13// ShellExecutor implements the CommandExecutor port using system shell commands
14type ShellExecutor struct {
15 logger ports.Logger
16}
17
18// NewShellExecutor creates a new shell executor with a logger
19func NewShellExecutor(logger ports.Logger) *ShellExecutor {
20 return &ShellExecutor{
21 logger: logger,
22 }
23}
24
25// Execute runs a command and returns output
26func (se *ShellExecutor) Execute(ctx context.Context, command string, args ...string) ([]byte, error) {
27 return se.run(ctx, "", nil, command, args...)
28}
29
30// ExecuteWithStdin runs a command with stdin content and returns output
31func (se *ShellExecutor) ExecuteWithStdin(ctx context.Context, stdin string, command string, args ...string) ([]byte, error) {
32 return se.run(ctx, stdin, nil, command, args...)
33}
34
35// ExecuteWithEnv runs a command with custom environment variables
36func (se *ShellExecutor) ExecuteWithEnv(ctx context.Context, env map[string]string, command string, args ...string) ([]byte, error) {
37 return se.run(ctx, "", env, command, args...)
38}
39
40func (se *ShellExecutor) run(ctx context.Context, stdin string, env map[string]string, command string, args ...string) ([]byte, error) {
41 cmd := exec.CommandContext(ctx, command, args...)
42 var stdout, stderr bytes.Buffer
43 cmd.Stdout = &stdout
44 cmd.Stderr = &stderr
45 if stdin != "" {
46 cmd.Stdin = bytes.NewBufferString(stdin)
47 }
48
49 if env != nil {
50 // Build environment: start with current environment, then add/override with provided env
51 cmdEnv := os.Environ()
52 for key, value := range env {
53 cmdEnv = append(cmdEnv, fmt.Sprintf("%s=%s", key, value))
54 }
55 cmd.Env = cmdEnv
56 }
57
58 if err := cmd.Run(); err != nil {
59 return nil, fmt.Errorf("command failed: %w (stderr: %s)", err, stderr.String())
60 }
61
62 return stdout.Bytes(), nil
63}