github.com/openebs/api@v1.12.0/pkg/util/exec-run.go (about)

     1  // Copyright © 2020 The OpenEBS Authors
     2  //
     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  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"io/ioutil"
    19  	"os"
    20  	"os/exec"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  
    25  	"context"
    26  
    27  	"k8s.io/klog"
    28  )
    29  
    30  // Runner interface implements various methods of running binaries which can be
    31  // modified for unit testing.
    32  type Runner interface {
    33  	RunCombinedOutput(string, ...string) ([]byte, error)
    34  	RunStdoutPipe(string, ...string) ([]byte, error)
    35  	RunCommandWithTimeoutContext(time.Duration, string, ...string) ([]byte, error)
    36  	RunCommandWithLog(string, ...string) ([]byte, error)
    37  }
    38  
    39  // RealRunner is the real runner for the program that actually execs the command.
    40  type RealRunner struct{}
    41  
    42  // RunCombinedOutput runs the command and returns its combined standard output
    43  // and standard error.
    44  func (r RealRunner) RunCombinedOutput(command string, args ...string) ([]byte, error) {
    45  	//execute pool creation command.
    46  	cmd := exec.Command(command, args...)
    47  	out, err := cmd.CombinedOutput()
    48  	return out, err
    49  }
    50  
    51  // RunStdoutPipe returns a pipe that will be connected to the command's standard output
    52  // when the command starts.
    53  func (r RealRunner) RunStdoutPipe(command string, args ...string) ([]byte, error) {
    54  	cmd := exec.Command(command, args...)
    55  
    56  	stdout, err := cmd.StdoutPipe()
    57  	if err != nil {
    58  		klog.Errorf(err.Error())
    59  		return []byte{}, err
    60  	}
    61  	if err := cmd.Start(); err != nil {
    62  		klog.Errorf(err.Error())
    63  		return []byte{}, err
    64  	}
    65  	data, _ := ioutil.ReadAll(stdout)
    66  	if err := cmd.Wait(); err != nil {
    67  		klog.Errorf(err.Error())
    68  		return []byte{}, err
    69  	}
    70  	return data, nil
    71  }
    72  
    73  // RunCommandWithLog triggers command passed as arguments and it also does the
    74  // following things before command completion
    75  // 1. Logs the stdout of command to stdout(standard output)
    76  // 2. Logs stderr of the command to standard error
    77  func (r RealRunner) RunCommandWithLog(command string, args ...string) ([]byte, error) {
    78  	// #nosec
    79  	cmd := exec.Command(command, args...)
    80  	// Redirect the command output to stdout
    81  	cmd.Stdout = os.Stdout
    82  	// Redirect the command output to stderr
    83  	cmd.Stderr = os.Stderr
    84  	// Start the command
    85  	if err := cmd.Start(); err != nil {
    86  		return []byte{}, err
    87  	}
    88  	// below will return error when command exit with return code 1
    89  	if err := cmd.Wait(); err != nil {
    90  		return []byte{}, err
    91  	}
    92  	return []byte{}, nil
    93  }
    94  
    95  // RunCommandWithTimeoutContext executes command provides and returns stdout
    96  // error. If command does not returns within given timout interval command will
    97  // be killed and return "Context time exceeded"
    98  func (r RealRunner) RunCommandWithTimeoutContext(timeout time.Duration, command string, args ...string) ([]byte, error) {
    99  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   100  	defer cancel()
   101  
   102  	out, err := exec.CommandContext(ctx, command, args...).CombinedOutput()
   103  	if err != nil {
   104  		select {
   105  		case <-ctx.Done():
   106  			return nil, errors.Wrapf(ctx.Err(), "Failed to run command: %v %v", command, args)
   107  		default:
   108  			return nil, err
   109  		}
   110  	}
   111  	return out, nil
   112  }
   113  
   114  //TestRunner is used as a dummy Runner
   115  type TestRunner struct{}
   116  
   117  // RunCombinedOutput is to mock Real runner exec.
   118  func (r TestRunner) RunCombinedOutput(command string, args ...string) ([]byte, error) {
   119  	return []byte("success"), nil
   120  }
   121  
   122  // RunStdoutPipe is to mock real runner exec with stdoutpipe.
   123  func (r TestRunner) RunStdoutPipe(command string, args ...string) ([]byte, error) {
   124  	return []byte("success"), nil
   125  }
   126  
   127  // RunCommandWithTimeoutContext is to mock Real runner exec.
   128  func (r TestRunner) RunCommandWithTimeoutContext(timeout time.Duration, command string, args ...string) ([]byte, error) {
   129  	return []byte("success"), nil
   130  }
   131  
   132  // RunCommandWithLog is to mock real runner exec with stdoutpipe.
   133  func (r TestRunner) RunCommandWithLog(command string, args ...string) ([]byte, error) {
   134  	return []byte("success"), nil
   135  }