exec.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  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 exec
  14. import (
  15. "io"
  16. osexec "os/exec"
  17. "syscall"
  18. )
  19. // ErrExecutableNotFound is returned if the executable is not found.
  20. var ErrExecutableNotFound = osexec.ErrNotFound
  21. // Interface is an interface that presents a subset of the os/exec API. Use this
  22. // when you want to inject fakeable/mockable exec behavior.
  23. type Interface interface {
  24. // Command returns a Cmd instance which can be used to run a single command.
  25. // This follows the pattern of package os/exec.
  26. Command(cmd string, args ...string) Cmd
  27. // LookPath wraps os/exec.LookPath
  28. LookPath(file string) (string, error)
  29. }
  30. // Cmd is an interface that presents an API that is very similar to Cmd from os/exec.
  31. // As more functionality is needed, this can grow. Since Cmd is a struct, we will have
  32. // to replace fields with get/set method pairs.
  33. type Cmd interface {
  34. // CombinedOutput runs the command and returns its combined standard output
  35. // and standard error. This follows the pattern of package os/exec.
  36. CombinedOutput() ([]byte, error)
  37. // Output runs the command and returns standard output, but not standard err
  38. Output() ([]byte, error)
  39. SetDir(dir string)
  40. SetStdin(in io.Reader)
  41. SetStdout(out io.Writer)
  42. }
  43. // ExitError is an interface that presents an API similar to os.ProcessState, which is
  44. // what ExitError from os/exec is. This is designed to make testing a bit easier and
  45. // probably loses some of the cross-platform properties of the underlying library.
  46. type ExitError interface {
  47. String() string
  48. Error() string
  49. Exited() bool
  50. ExitStatus() int
  51. }
  52. // Implements Interface in terms of really exec()ing.
  53. type executor struct{}
  54. // New returns a new Interface which will os/exec to run commands.
  55. func New() Interface {
  56. return &executor{}
  57. }
  58. // Command is part of the Interface interface.
  59. func (executor *executor) Command(cmd string, args ...string) Cmd {
  60. return (*cmdWrapper)(osexec.Command(cmd, args...))
  61. }
  62. // LookPath is part of the Interface interface
  63. func (executor *executor) LookPath(file string) (string, error) {
  64. return osexec.LookPath(file)
  65. }
  66. // Wraps exec.Cmd so we can capture errors.
  67. type cmdWrapper osexec.Cmd
  68. func (cmd *cmdWrapper) SetDir(dir string) {
  69. cmd.Dir = dir
  70. }
  71. func (cmd *cmdWrapper) SetStdin(in io.Reader) {
  72. cmd.Stdin = in
  73. }
  74. func (cmd *cmdWrapper) SetStdout(out io.Writer) {
  75. cmd.Stdout = out
  76. }
  77. // CombinedOutput is part of the Cmd interface.
  78. func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) {
  79. out, err := (*osexec.Cmd)(cmd).CombinedOutput()
  80. if err != nil {
  81. return out, handleError(err)
  82. }
  83. return out, nil
  84. }
  85. func (cmd *cmdWrapper) Output() ([]byte, error) {
  86. out, err := (*osexec.Cmd)(cmd).Output()
  87. if err != nil {
  88. return out, handleError(err)
  89. }
  90. return out, nil
  91. }
  92. func handleError(err error) error {
  93. if ee, ok := err.(*osexec.ExitError); ok {
  94. // Force a compile fail if exitErrorWrapper can't convert to ExitError.
  95. var x ExitError = &ExitErrorWrapper{ee}
  96. return x
  97. }
  98. if ee, ok := err.(*osexec.Error); ok {
  99. if ee.Err == osexec.ErrNotFound {
  100. return ErrExecutableNotFound
  101. }
  102. }
  103. return err
  104. }
  105. // ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError.
  106. // Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited().
  107. type ExitErrorWrapper struct {
  108. *osexec.ExitError
  109. }
  110. var _ ExitError = ExitErrorWrapper{}
  111. // ExitStatus is part of the ExitError interface.
  112. func (eew ExitErrorWrapper) ExitStatus() int {
  113. ws, ok := eew.Sys().(syscall.WaitStatus)
  114. if !ok {
  115. panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper")
  116. }
  117. return ws.ExitStatus()
  118. }
  119. // CodeExitError is an implementation of ExitError consisting of an error object
  120. // and an exit code (the upper bits of os.exec.ExitStatus).
  121. type CodeExitError struct {
  122. Err error
  123. Code int
  124. }
  125. var _ ExitError = CodeExitError{}
  126. func (e CodeExitError) Error() string {
  127. return e.Err.Error()
  128. }
  129. func (e CodeExitError) String() string {
  130. return e.Err.Error()
  131. }
  132. func (e CodeExitError) Exited() bool {
  133. return true
  134. }
  135. func (e CodeExitError) ExitStatus() int {
  136. return e.Code
  137. }