Files
ReleaseKit/internal/process/process.go

61 lines
1.6 KiB
Go

// Package process runs child commands for ReleaseKit's internal adapters.
package process
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"sort"
)
// Invocation describes one child process. Environment values overlay the
// current process environment for this child only.
type Invocation struct {
Args []string
Dir string
Env map[string]string
Stdout io.Writer
Stderr io.Writer
}
// Run starts the command in the requested directory and waits for it to exit.
// Errors preserve the underlying exec error, including *exec.ExitError.
func Run(ctx context.Context, invocation Invocation) error {
if len(invocation.Args) == 0 || invocation.Args[0] == "" {
return errors.New("process invocation requires a command")
}
command := exec.CommandContext(ctx, invocation.Args[0], invocation.Args[1:]...)
command.Dir = invocation.Dir
if len(invocation.Env) != 0 {
command.Env = append(os.Environ(), environmentEntries(invocation.Env)...)
}
command.Stdout = invocation.Stdout
command.Stderr = invocation.Stderr
if err := command.Run(); err != nil {
if contextErr := ctx.Err(); contextErr != nil {
return fmt.Errorf("run %q: %w", invocation.Args[0], contextErr)
}
return fmt.Errorf("run %q: %w", invocation.Args[0], err)
}
return nil
}
func environmentEntries(environment map[string]string) []string {
keys := make([]string, 0, len(environment))
for key := range environment {
keys = append(keys, key)
}
sort.Strings(keys)
entries := make([]string, 0, len(keys))
for _, key := range keys {
entries = append(entries, key+"="+environment[key])
}
return entries
}