exec.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. Copyright 2020 Docker, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package framework
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "os/exec"
  19. "runtime"
  20. "strings"
  21. "syscall"
  22. "time"
  23. "github.com/onsi/gomega"
  24. "github.com/sirupsen/logrus"
  25. )
  26. func (b CmdContext) makeCmd() *exec.Cmd {
  27. return exec.Command(b.command, b.args...)
  28. }
  29. // CmdContext is used to build, customize and execute a command.
  30. // Add more functions to customize the context as needed.
  31. type CmdContext struct {
  32. command string
  33. args []string
  34. envs []string
  35. dir string
  36. stdin io.Reader
  37. timeout <-chan time.Time
  38. retries RetriesContext
  39. }
  40. // RetriesContext is used to tweak retry loop.
  41. type RetriesContext struct {
  42. count int
  43. interval time.Duration
  44. }
  45. // WithinDirectory tells Docker the cwd.
  46. func (b *CmdContext) WithinDirectory(path string) *CmdContext {
  47. b.dir = path
  48. return b
  49. }
  50. // WithEnvs set envs in context.
  51. func (b *CmdContext) WithEnvs(envs []string) *CmdContext {
  52. b.envs = envs
  53. return b
  54. }
  55. // WithTimeout controls maximum duration.
  56. func (b *CmdContext) WithTimeout(t <-chan time.Time) *CmdContext {
  57. b.timeout = t
  58. return b
  59. }
  60. // WithRetries sets how many times to retry the command before issuing an error
  61. func (b *CmdContext) WithRetries(count int) *CmdContext {
  62. b.retries.count = count
  63. return b
  64. }
  65. // Every interval between 2 retries
  66. func (b *CmdContext) Every(interval time.Duration) *CmdContext {
  67. b.retries.interval = interval
  68. return b
  69. }
  70. // WithStdinData feeds via stdin.
  71. func (b CmdContext) WithStdinData(data string) *CmdContext {
  72. b.stdin = strings.NewReader(data)
  73. return &b
  74. }
  75. // WithStdinReader feeds via stdin.
  76. func (b CmdContext) WithStdinReader(reader io.Reader) *CmdContext {
  77. b.stdin = reader
  78. return &b
  79. }
  80. // ExecOrDie runs a docker command.
  81. func (b CmdContext) ExecOrDie() string {
  82. str, err := b.Exec()
  83. logrus.Debugf("stdout: %s", str)
  84. gomega.Expect(err).NotTo(gomega.HaveOccurred())
  85. return str
  86. }
  87. // Exec runs a docker command.
  88. func (b CmdContext) Exec() (string, error) {
  89. retry := b.retries.count
  90. for ; ; retry-- {
  91. cmd := b.makeCmd()
  92. cmd.Dir = b.dir
  93. cmd.Stdin = b.stdin
  94. if b.envs != nil {
  95. cmd.Env = b.envs
  96. }
  97. stdout, err := Execute(cmd, b.timeout)
  98. if err == nil || retry < 1 {
  99. return stdout, err
  100. }
  101. time.Sleep(b.retries.interval)
  102. }
  103. }
  104. //WaitFor waits for a condition to be true
  105. func WaitFor(interval, duration time.Duration, abort <-chan error, condition func() bool) error {
  106. ticker := time.NewTicker(interval)
  107. defer ticker.Stop()
  108. timeout := make(chan int)
  109. go func() {
  110. time.Sleep(duration)
  111. close(timeout)
  112. }()
  113. for {
  114. select {
  115. case err := <-abort:
  116. return err
  117. case <-timeout:
  118. return fmt.Errorf("timeout after %v", duration)
  119. case <-ticker.C:
  120. if condition() {
  121. return nil
  122. }
  123. }
  124. }
  125. }
  126. // Execute executes a command.
  127. // The command cannot be re-used afterwards.
  128. func Execute(cmd *exec.Cmd, timeout <-chan time.Time) (string, error) {
  129. var stdout, stderr bytes.Buffer
  130. cmd.Stdout = mergeWriter(cmd.Stdout, &stdout)
  131. cmd.Stderr = mergeWriter(cmd.Stderr, &stderr)
  132. logrus.Infof("Execute '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately
  133. if err := cmd.Start(); err != nil {
  134. return "", fmt.Errorf("error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, stdout.String(), stderr.String(), err)
  135. }
  136. errCh := make(chan error, 1)
  137. go func() {
  138. errCh <- cmd.Wait()
  139. }()
  140. select {
  141. case err := <-errCh:
  142. if err != nil {
  143. logrus.Debugf("%s %s failed: %v", cmd.Path, strings.Join(cmd.Args[1:], " "), err)
  144. return stderr.String(), fmt.Errorf("error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, stdout.String(), stderr.String(), err)
  145. }
  146. case <-timeout:
  147. logrus.Debugf("%s %s timed-out", cmd.Path, strings.Join(cmd.Args[1:], " "))
  148. if err := terminateProcess(cmd); err != nil {
  149. return "", err
  150. }
  151. return stdout.String(), fmt.Errorf(
  152. "timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v",
  153. cmd.Args, stdout.String(), stderr.String())
  154. }
  155. if stderr.String() != "" {
  156. logrus.Debugf("stderr: %s", stderr.String())
  157. }
  158. return stdout.String(), nil
  159. }
  160. func terminateProcess(cmd *exec.Cmd) error {
  161. if runtime.GOOS == "windows" {
  162. return cmd.Process.Kill()
  163. }
  164. return cmd.Process.Signal(syscall.SIGTERM)
  165. }
  166. func mergeWriter(other io.Writer, buf io.Writer) io.Writer {
  167. if other != nil {
  168. return io.MultiWriter(other, buf)
  169. }
  170. return buf
  171. }
  172. // Powershell runs a powershell command.
  173. func Powershell(input string) (string, error) {
  174. output, err := Execute(exec.Command("powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Unrestricted", "-Command", input), nil)
  175. if err != nil {
  176. return "", fmt.Errorf("fail to execute %s: %s", input, err)
  177. }
  178. return strings.TrimSpace(output), nil
  179. }