mirror of
https://github.com/dokku/dokku.git
synced 2025-12-16 20:17:44 +01:00
203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
package common
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"context"
|
|
|
|
execute "github.com/alexellis/go-execute/v2"
|
|
"github.com/fatih/color"
|
|
)
|
|
|
|
// ExecCommandInput is the input for the ExecCommand function
|
|
type ExecCommandInput struct {
|
|
// Command is the command to execute
|
|
Command string
|
|
|
|
// Args are the arguments to pass to the command
|
|
Args []string
|
|
|
|
// DisableStdioBuffer disables the stdio buffer
|
|
DisableStdioBuffer bool
|
|
|
|
// Env is the environment variables to pass to the command
|
|
Env map[string]string
|
|
|
|
// PrintCommand prints the command before executing
|
|
PrintCommand bool
|
|
|
|
// Stdin is the stdin of the command
|
|
Stdin io.Reader
|
|
|
|
// StreamStdio prints stdout and stderr directly to os.Stdout/err as
|
|
// the command runs
|
|
StreamStdio bool
|
|
|
|
// StreamStdout prints stdout directly to os.Stdout as the command runs.
|
|
StreamStdout bool
|
|
|
|
// StreamStderr prints stderr directly to os.Stderr as the command runs.
|
|
StreamStderr bool
|
|
|
|
// StdoutWriter is the writer to write stdout to
|
|
StdoutWriter io.Writer
|
|
|
|
// StderrWriter is the writer to write stderr to
|
|
StderrWriter io.Writer
|
|
|
|
// Sudo runs the command with sudo -n -u root
|
|
Sudo bool
|
|
|
|
// WorkingDirectory is the working directory to run the command in
|
|
WorkingDirectory string
|
|
}
|
|
|
|
// ExecCommandResponse is the response for the ExecCommand function
|
|
type ExecCommandResponse struct {
|
|
// Stdout is the stdout of the command
|
|
Stdout string
|
|
|
|
// Stderr is the stderr of the command
|
|
Stderr string
|
|
|
|
// ExitCode is the exit code of the command
|
|
ExitCode int
|
|
|
|
// Cancelled is whether the command was cancelled
|
|
Cancelled bool
|
|
}
|
|
|
|
// StdoutContents returns the trimmed stdout of the command
|
|
func (ecr ExecCommandResponse) StdoutContents() string {
|
|
return strings.TrimSpace(ecr.Stdout)
|
|
}
|
|
|
|
// StderrContents returns the trimmed stderr of the command
|
|
func (ecr ExecCommandResponse) StderrContents() string {
|
|
return strings.TrimSpace(ecr.Stderr)
|
|
}
|
|
|
|
// StderrBytes returns the trimmed stderr of the command as bytes
|
|
func (ecr ExecCommandResponse) StderrBytes() []byte {
|
|
return []byte(ecr.StderrContents())
|
|
}
|
|
|
|
// StdoutBytes returns the trimmed stdout of the command as bytes
|
|
func (ecr ExecCommandResponse) StdoutBytes() []byte {
|
|
return []byte(ecr.StdoutContents())
|
|
}
|
|
|
|
// CallExecCommand executes a command on the local host
|
|
func CallExecCommand(input ExecCommandInput) (ExecCommandResponse, error) {
|
|
ctx := context.Background()
|
|
return CallExecCommandWithContext(ctx, input)
|
|
}
|
|
|
|
// CallExecCommandWithContext executes a command on the local host with the given context
|
|
func CallExecCommandWithContext(ctx context.Context, input ExecCommandInput) (ExecCommandResponse, error) {
|
|
signals := make(chan os.Signal, 1)
|
|
signal.Notify(signals, os.Interrupt, syscall.SIGHUP,
|
|
syscall.SIGINT,
|
|
syscall.SIGQUIT,
|
|
syscall.SIGTERM)
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
go func() {
|
|
<-signals
|
|
cancel()
|
|
}()
|
|
|
|
// hack: colors do not work natively with io.MultiWriter
|
|
// as it isn't detected as a tty. If the output isn't
|
|
// being captured, then color output can be forced.
|
|
isatty := !color.NoColor
|
|
env := os.Environ()
|
|
if isatty && input.DisableStdioBuffer {
|
|
env = append(env, "FORCE_TTY=1")
|
|
}
|
|
if input.Env != nil {
|
|
for k, v := range input.Env {
|
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
}
|
|
|
|
command := input.Command
|
|
commandArgs := input.Args
|
|
if input.Sudo {
|
|
commandArgs = append([]string{"-n", "-u", "root", command}, commandArgs...)
|
|
command = "sudo"
|
|
}
|
|
|
|
cmd := execute.ExecTask{
|
|
Command: command,
|
|
Args: commandArgs,
|
|
Env: env,
|
|
DisableStdioBuffer: input.DisableStdioBuffer,
|
|
}
|
|
|
|
if input.WorkingDirectory != "" {
|
|
cmd.Cwd = input.WorkingDirectory
|
|
}
|
|
|
|
if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
|
|
argsSt := ""
|
|
if len(cmd.Args) > 0 {
|
|
argsSt = strings.Join(cmd.Args, " ")
|
|
}
|
|
LogWarn(fmt.Sprintf("exec: %s %s", cmd.Command, argsSt))
|
|
}
|
|
|
|
if input.Stdin != nil {
|
|
cmd.Stdin = input.Stdin
|
|
} else if isatty {
|
|
cmd.Stdin = os.Stdin
|
|
}
|
|
|
|
if input.StreamStdio {
|
|
cmd.StreamStdio = true
|
|
}
|
|
if input.StreamStdout {
|
|
cmd.StdOutWriter = os.Stdout
|
|
}
|
|
if input.StreamStderr {
|
|
cmd.StdErrWriter = os.Stderr
|
|
}
|
|
if input.StdoutWriter != nil {
|
|
cmd.StdOutWriter = input.StdoutWriter
|
|
}
|
|
if input.StderrWriter != nil {
|
|
cmd.StdErrWriter = input.StderrWriter
|
|
}
|
|
|
|
res, err := cmd.Execute(ctx)
|
|
if err != nil {
|
|
return ExecCommandResponse{
|
|
Stdout: res.Stdout,
|
|
Stderr: res.Stderr,
|
|
ExitCode: res.ExitCode,
|
|
Cancelled: res.Cancelled,
|
|
}, err
|
|
}
|
|
|
|
if res.ExitCode != 0 {
|
|
return ExecCommandResponse{
|
|
Stdout: res.Stdout,
|
|
Stderr: res.Stderr,
|
|
ExitCode: res.ExitCode,
|
|
Cancelled: res.Cancelled,
|
|
}, errors.New(res.Stderr)
|
|
}
|
|
|
|
return ExecCommandResponse{
|
|
Stdout: res.Stdout,
|
|
Stderr: res.Stderr,
|
|
ExitCode: res.ExitCode,
|
|
Cancelled: res.Cancelled,
|
|
}, nil
|
|
}
|