...

Source file src/k8s.io/utils/exec/exec.go

Documentation: k8s.io/utils/exec

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package exec
    18  
    19  import (
    20  	"context"
    21  	"io"
    22  	"io/fs"
    23  	osexec "os/exec"
    24  	"syscall"
    25  	"time"
    26  )
    27  
    28  // ErrExecutableNotFound is returned if the executable is not found.
    29  var ErrExecutableNotFound = osexec.ErrNotFound
    30  
    31  // Interface is an interface that presents a subset of the os/exec API. Use this
    32  // when you want to inject fakeable/mockable exec behavior.
    33  type Interface interface {
    34  	// Command returns a Cmd instance which can be used to run a single command.
    35  	// This follows the pattern of package os/exec.
    36  	Command(cmd string, args ...string) Cmd
    37  
    38  	// CommandContext returns a Cmd instance which can be used to run a single command.
    39  	//
    40  	// The provided context is used to kill the process if the context becomes done
    41  	// before the command completes on its own. For example, a timeout can be set in
    42  	// the context.
    43  	CommandContext(ctx context.Context, cmd string, args ...string) Cmd
    44  
    45  	// LookPath wraps os/exec.LookPath
    46  	LookPath(file string) (string, error)
    47  }
    48  
    49  // Cmd is an interface that presents an API that is very similar to Cmd from os/exec.
    50  // As more functionality is needed, this can grow. Since Cmd is a struct, we will have
    51  // to replace fields with get/set method pairs.
    52  type Cmd interface {
    53  	// Run runs the command to the completion.
    54  	Run() error
    55  	// CombinedOutput runs the command and returns its combined standard output
    56  	// and standard error. This follows the pattern of package os/exec.
    57  	CombinedOutput() ([]byte, error)
    58  	// Output runs the command and returns standard output, but not standard err
    59  	Output() ([]byte, error)
    60  	SetDir(dir string)
    61  	SetStdin(in io.Reader)
    62  	SetStdout(out io.Writer)
    63  	SetStderr(out io.Writer)
    64  	SetEnv(env []string)
    65  
    66  	// StdoutPipe and StderrPipe for getting the process' Stdout and Stderr as
    67  	// Readers
    68  	StdoutPipe() (io.ReadCloser, error)
    69  	StderrPipe() (io.ReadCloser, error)
    70  
    71  	// Start and Wait are for running a process non-blocking
    72  	Start() error
    73  	Wait() error
    74  
    75  	// Stops the command by sending SIGTERM. It is not guaranteed the
    76  	// process will stop before this function returns. If the process is not
    77  	// responding, an internal timer function will send a SIGKILL to force
    78  	// terminate after 10 seconds.
    79  	Stop()
    80  }
    81  
    82  // ExitError is an interface that presents an API similar to os.ProcessState, which is
    83  // what ExitError from os/exec is. This is designed to make testing a bit easier and
    84  // probably loses some of the cross-platform properties of the underlying library.
    85  type ExitError interface {
    86  	String() string
    87  	Error() string
    88  	Exited() bool
    89  	ExitStatus() int
    90  }
    91  
    92  // Implements Interface in terms of really exec()ing.
    93  type executor struct{}
    94  
    95  // New returns a new Interface which will os/exec to run commands.
    96  func New() Interface {
    97  	return &executor{}
    98  }
    99  
   100  // Command is part of the Interface interface.
   101  func (executor *executor) Command(cmd string, args ...string) Cmd {
   102  	return (*cmdWrapper)(maskErrDotCmd(osexec.Command(cmd, args...)))
   103  }
   104  
   105  // CommandContext is part of the Interface interface.
   106  func (executor *executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd {
   107  	return (*cmdWrapper)(maskErrDotCmd(osexec.CommandContext(ctx, cmd, args...)))
   108  }
   109  
   110  // LookPath is part of the Interface interface
   111  func (executor *executor) LookPath(file string) (string, error) {
   112  	path, err := osexec.LookPath(file)
   113  	return path, handleError(maskErrDot(err))
   114  }
   115  
   116  // Wraps exec.Cmd so we can capture errors.
   117  type cmdWrapper osexec.Cmd
   118  
   119  var _ Cmd = &cmdWrapper{}
   120  
   121  func (cmd *cmdWrapper) SetDir(dir string) {
   122  	cmd.Dir = dir
   123  }
   124  
   125  func (cmd *cmdWrapper) SetStdin(in io.Reader) {
   126  	cmd.Stdin = in
   127  }
   128  
   129  func (cmd *cmdWrapper) SetStdout(out io.Writer) {
   130  	cmd.Stdout = out
   131  }
   132  
   133  func (cmd *cmdWrapper) SetStderr(out io.Writer) {
   134  	cmd.Stderr = out
   135  }
   136  
   137  func (cmd *cmdWrapper) SetEnv(env []string) {
   138  	cmd.Env = env
   139  }
   140  
   141  func (cmd *cmdWrapper) StdoutPipe() (io.ReadCloser, error) {
   142  	r, err := (*osexec.Cmd)(cmd).StdoutPipe()
   143  	return r, handleError(err)
   144  }
   145  
   146  func (cmd *cmdWrapper) StderrPipe() (io.ReadCloser, error) {
   147  	r, err := (*osexec.Cmd)(cmd).StderrPipe()
   148  	return r, handleError(err)
   149  }
   150  
   151  func (cmd *cmdWrapper) Start() error {
   152  	err := (*osexec.Cmd)(cmd).Start()
   153  	return handleError(err)
   154  }
   155  
   156  func (cmd *cmdWrapper) Wait() error {
   157  	err := (*osexec.Cmd)(cmd).Wait()
   158  	return handleError(err)
   159  }
   160  
   161  // Run is part of the Cmd interface.
   162  func (cmd *cmdWrapper) Run() error {
   163  	err := (*osexec.Cmd)(cmd).Run()
   164  	return handleError(err)
   165  }
   166  
   167  // CombinedOutput is part of the Cmd interface.
   168  func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) {
   169  	out, err := (*osexec.Cmd)(cmd).CombinedOutput()
   170  	return out, handleError(err)
   171  }
   172  
   173  func (cmd *cmdWrapper) Output() ([]byte, error) {
   174  	out, err := (*osexec.Cmd)(cmd).Output()
   175  	return out, handleError(err)
   176  }
   177  
   178  // Stop is part of the Cmd interface.
   179  func (cmd *cmdWrapper) Stop() {
   180  	c := (*osexec.Cmd)(cmd)
   181  
   182  	if c.Process == nil {
   183  		return
   184  	}
   185  
   186  	c.Process.Signal(syscall.SIGTERM)
   187  
   188  	time.AfterFunc(10*time.Second, func() {
   189  		if !c.ProcessState.Exited() {
   190  			c.Process.Signal(syscall.SIGKILL)
   191  		}
   192  	})
   193  }
   194  
   195  func handleError(err error) error {
   196  	if err == nil {
   197  		return nil
   198  	}
   199  
   200  	switch e := err.(type) {
   201  	case *osexec.ExitError:
   202  		return &ExitErrorWrapper{e}
   203  	case *fs.PathError:
   204  		return ErrExecutableNotFound
   205  	case *osexec.Error:
   206  		if e.Err == osexec.ErrNotFound {
   207  			return ErrExecutableNotFound
   208  		}
   209  	}
   210  
   211  	return err
   212  }
   213  
   214  // ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError.
   215  // Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited().
   216  type ExitErrorWrapper struct {
   217  	*osexec.ExitError
   218  }
   219  
   220  var _ ExitError = &ExitErrorWrapper{}
   221  
   222  // ExitStatus is part of the ExitError interface.
   223  func (eew ExitErrorWrapper) ExitStatus() int {
   224  	ws, ok := eew.Sys().(syscall.WaitStatus)
   225  	if !ok {
   226  		panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper")
   227  	}
   228  	return ws.ExitStatus()
   229  }
   230  
   231  // CodeExitError is an implementation of ExitError consisting of an error object
   232  // and an exit code (the upper bits of os.exec.ExitStatus).
   233  type CodeExitError struct {
   234  	Err  error
   235  	Code int
   236  }
   237  
   238  var _ ExitError = CodeExitError{}
   239  
   240  func (e CodeExitError) Error() string {
   241  	return e.Err.Error()
   242  }
   243  
   244  func (e CodeExitError) String() string {
   245  	return e.Err.Error()
   246  }
   247  
   248  // Exited is to check if the process has finished
   249  func (e CodeExitError) Exited() bool {
   250  	return true
   251  }
   252  
   253  // ExitStatus is for checking the error code
   254  func (e CodeExitError) ExitStatus() int {
   255  	return e.Code
   256  }
   257  

View as plain text