utils.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package client
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "os"
  7. gosignal "os/signal"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "time"
  12. "golang.org/x/net/context"
  13. "github.com/Sirupsen/logrus"
  14. "github.com/docker/docker/pkg/signal"
  15. "github.com/docker/docker/pkg/term"
  16. "github.com/docker/engine-api/client"
  17. "github.com/docker/engine-api/types"
  18. )
  19. func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
  20. height, width := cli.GetTtySize()
  21. cli.ResizeTtyTo(ctx, id, height, width, isExec)
  22. }
  23. // ResizeTtyTo resizes tty to specific height and width
  24. // TODO: this can be unexported again once all container related commands move to package container
  25. func (cli *DockerCli) ResizeTtyTo(ctx context.Context, id string, height, width int, isExec bool) {
  26. if height == 0 && width == 0 {
  27. return
  28. }
  29. options := types.ResizeOptions{
  30. Height: height,
  31. Width: width,
  32. }
  33. var err error
  34. if isExec {
  35. err = cli.client.ContainerExecResize(ctx, id, options)
  36. } else {
  37. err = cli.client.ContainerResize(ctx, id, options)
  38. }
  39. if err != nil {
  40. logrus.Debugf("Error resize: %s", err)
  41. }
  42. }
  43. // GetExecExitCode perform an inspect on the exec command. It returns
  44. // the running state and the exit code.
  45. func (cli *DockerCli) GetExecExitCode(ctx context.Context, execID string) (bool, int, error) {
  46. resp, err := cli.client.ContainerExecInspect(ctx, execID)
  47. if err != nil {
  48. // If we can't connect, then the daemon probably died.
  49. if err != client.ErrConnectionFailed {
  50. return false, -1, err
  51. }
  52. return false, -1, nil
  53. }
  54. return resp.Running, resp.ExitCode, nil
  55. }
  56. // MonitorTtySize updates the container tty size when the terminal tty changes size
  57. func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error {
  58. cli.resizeTty(ctx, id, isExec)
  59. if runtime.GOOS == "windows" {
  60. go func() {
  61. prevH, prevW := cli.GetTtySize()
  62. for {
  63. time.Sleep(time.Millisecond * 250)
  64. h, w := cli.GetTtySize()
  65. if prevW != w || prevH != h {
  66. cli.resizeTty(ctx, id, isExec)
  67. }
  68. prevH = h
  69. prevW = w
  70. }
  71. }()
  72. } else {
  73. sigchan := make(chan os.Signal, 1)
  74. gosignal.Notify(sigchan, signal.SIGWINCH)
  75. go func() {
  76. for range sigchan {
  77. cli.resizeTty(ctx, id, isExec)
  78. }
  79. }()
  80. }
  81. return nil
  82. }
  83. // GetTtySize returns the height and width in characters of the tty
  84. func (cli *DockerCli) GetTtySize() (int, int) {
  85. if !cli.isTerminalOut {
  86. return 0, 0
  87. }
  88. ws, err := term.GetWinsize(cli.outFd)
  89. if err != nil {
  90. logrus.Debugf("Error getting size: %s", err)
  91. if ws == nil {
  92. return 0, 0
  93. }
  94. }
  95. return int(ws.Height), int(ws.Width)
  96. }
  97. // CopyToFile writes the content of the reader to the specified file
  98. func CopyToFile(outfile string, r io.Reader) error {
  99. tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
  100. if err != nil {
  101. return err
  102. }
  103. tmpPath := tmpFile.Name()
  104. _, err = io.Copy(tmpFile, r)
  105. tmpFile.Close()
  106. if err != nil {
  107. os.Remove(tmpPath)
  108. return err
  109. }
  110. if err = os.Rename(tmpPath, outfile); err != nil {
  111. os.Remove(tmpPath)
  112. return err
  113. }
  114. return nil
  115. }
  116. // ForwardAllSignals forwards signals to the container
  117. // TODO: this can be unexported again once all container commands are under
  118. // api/client/container
  119. func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
  120. sigc := make(chan os.Signal, 128)
  121. signal.CatchAll(sigc)
  122. go func() {
  123. for s := range sigc {
  124. if s == signal.SIGCHLD || s == signal.SIGPIPE {
  125. continue
  126. }
  127. var sig string
  128. for sigStr, sigN := range signal.SignalMap {
  129. if sigN == s {
  130. sig = sigStr
  131. break
  132. }
  133. }
  134. if sig == "" {
  135. fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
  136. continue
  137. }
  138. if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
  139. logrus.Debugf("Error sending signal: %s", err)
  140. }
  141. }
  142. }()
  143. return sigc
  144. }
  145. // capitalizeFirst capitalizes the first character of string
  146. func capitalizeFirst(s string) string {
  147. switch l := len(s); l {
  148. case 0:
  149. return s
  150. case 1:
  151. return strings.ToLower(s)
  152. default:
  153. return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
  154. }
  155. }
  156. // PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
  157. func PrettyPrint(i interface{}) string {
  158. switch t := i.(type) {
  159. case nil:
  160. return "None"
  161. case string:
  162. return capitalizeFirst(t)
  163. default:
  164. return capitalizeFirst(fmt.Sprintf("%s", t))
  165. }
  166. }