nginx-ui/internal/docker/exec.go
2025-04-20 22:02:29 +08:00

80 lines
2.4 KiB
Go

package docker
import (
"bytes"
"context"
"io"
"strconv"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
)
// Exec executes a command in a specific container and returns the output.
func Exec(ctx context.Context, command []string) (string, error) {
if !settings.NginxSettings.RunningInAnotherContainer() {
return "", ErrNginxNotRunningInAnotherContainer
}
cli, err := initClient()
if err != nil {
return "", cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
}
defer cli.Close()
execConfig := container.ExecOptions{
AttachStdout: true,
AttachStderr: true, // Also attach stderr to capture errors from the command
Cmd: command,
}
// Create the exec instance
execCreateResp, err := cli.ContainerExecCreate(ctx, settings.NginxSettings.ContainerName, execConfig)
if err != nil {
return "", cosy.WrapErrorWithParams(ErrFailedToExec, err.Error())
}
execID := execCreateResp.ID
// Attach to the exec instance
hijackedResp, err := cli.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{})
if err != nil {
return "", cosy.WrapErrorWithParams(ErrFailedToAttach, err.Error())
}
defer hijackedResp.Close()
// Read the output
var outBuf, errBuf bytes.Buffer
outputDone := make(chan error)
go func() {
// stdcopy.StdCopy demultiplexes the stream into two buffers
_, err = stdcopy.StdCopy(&outBuf, &errBuf, hijackedResp.Reader)
outputDone <- err
}()
select {
case err := <-outputDone:
if err != nil && err != io.EOF { // io.EOF is expected when the stream finishes
return "", cosy.WrapErrorWithParams(ErrReadOutput, err.Error())
}
case <-ctx.Done():
return "", cosy.WrapErrorWithParams(ErrReadOutput, ctx.Err().Error())
}
// Optionally inspect the exec process to check the exit code
execInspectResp, err := cli.ContainerExecInspect(ctx, execID)
logger.Debug("docker exec result", outBuf.String(), errBuf.String())
if err != nil {
return "", cosy.WrapErrorWithParams(ErrExitUnexpected, err.Error())
} else if execInspectResp.ExitCode != 0 {
// Command exited with a non-zero status code. Return stderr as part of the error.
return outBuf.String(), cosy.WrapErrorWithParams(ErrExitUnexpected, strconv.Itoa(execInspectResp.ExitCode), errBuf.String())
}
// Return stdout if successful
return outBuf.String(), nil
}