exec.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. package docker
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "strconv"
  7. "github.com/0xJacky/Nginx-UI/settings"
  8. "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/pkg/stdcopy"
  10. "github.com/uozi-tech/cosy"
  11. "github.com/uozi-tech/cosy/logger"
  12. )
  13. // Exec executes a command in a specific container and returns the output.
  14. func Exec(ctx context.Context, command []string) (string, error) {
  15. if !settings.NginxSettings.RunningInAnotherContainer() {
  16. return "", ErrNginxNotRunningInAnotherContainer
  17. }
  18. cli, err := initClient()
  19. if err != nil {
  20. return "", cosy.WrapErrorWithParams(ErrClientNotInitialized, err.Error())
  21. }
  22. defer cli.Close()
  23. execConfig := container.ExecOptions{
  24. AttachStdout: true,
  25. AttachStderr: true, // Also attach stderr to capture errors from the command
  26. Cmd: command,
  27. }
  28. // Create the exec instance
  29. execCreateResp, err := cli.ContainerExecCreate(ctx, settings.NginxSettings.ContainerName, execConfig)
  30. if err != nil {
  31. return "", cosy.WrapErrorWithParams(ErrFailedToExec, err.Error())
  32. }
  33. execID := execCreateResp.ID
  34. // Attach to the exec instance
  35. hijackedResp, err := cli.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{})
  36. if err != nil {
  37. return "", cosy.WrapErrorWithParams(ErrFailedToAttach, err.Error())
  38. }
  39. defer hijackedResp.Close()
  40. // Read the output
  41. var outBuf, errBuf bytes.Buffer
  42. outputDone := make(chan error)
  43. go func() {
  44. // stdcopy.StdCopy demultiplexes the stream into two buffers
  45. _, err = stdcopy.StdCopy(&outBuf, &errBuf, hijackedResp.Reader)
  46. outputDone <- err
  47. }()
  48. select {
  49. case err := <-outputDone:
  50. if err != nil && err != io.EOF { // io.EOF is expected when the stream finishes
  51. return "", cosy.WrapErrorWithParams(ErrReadOutput, err.Error())
  52. }
  53. case <-ctx.Done():
  54. return "", cosy.WrapErrorWithParams(ErrReadOutput, ctx.Err().Error())
  55. }
  56. // Optionally inspect the exec process to check the exit code
  57. execInspectResp, err := cli.ContainerExecInspect(ctx, execID)
  58. logger.Debug("docker exec result", outBuf.String(), errBuf.String())
  59. if err != nil {
  60. return "", cosy.WrapErrorWithParams(ErrExitUnexpected, err.Error())
  61. } else if execInspectResp.ExitCode != 0 {
  62. // Command exited with a non-zero status code. Return stderr as part of the error.
  63. return outBuf.String(), cosy.WrapErrorWithParams(ErrExitUnexpected, strconv.Itoa(execInspectResp.ExitCode), errBuf.String())
  64. }
  65. // Return stdout if successful
  66. return outBuf.String(), nil
  67. }