github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/biz/impl/shellfunc.go (about)

     1  // Ultimate Provisioner: UP cmd
     2  // Copyright (c) 2019 Stephen Cheng and contributors
     3  
     4  /* This Source Code Form is subject to the terms of the Mozilla Public
     5   * License, v. 2.0. If a copy of the MPL was not distributed with this
     6   * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
     7  
     8  package impl
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"github.com/fatih/color"
    14  	ms "github.com/mitchellh/mapstructure"
    15  	"github.com/upcmd/up/model/core"
    16  	u "github.com/upcmd/up/utils"
    17  	"io"
    18  	"os"
    19  	"os/exec"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  )
    25  
    26  func runCmd(f *ShellFuncAction, cmd string, idx1 int) {
    27  	var result u.ExecResult
    28  	result.Cmd = cmd
    29  	if TaskerRuntime().Tasker.Dryrun {
    30  		u.Pdryrun("in dryrun mode and skipping the actual commands")
    31  		result.Code = 0
    32  		result.Output = strings.TrimSpace("dryrun result")
    33  	} else {
    34  		switch u.MainConfig.ShellType {
    35  		case "GOSH":
    36  			envvarObjMap := f.Vars.GetPrefixMatched("envVar_")
    37  			envVars := map[string]string{}
    38  			for k, v := range *envvarObjMap {
    39  				envVars[k] = v.(string)
    40  			}
    41  
    42  			result = u.RunCmd(cmd,
    43  				"",
    44  				&envVars,
    45  			)
    46  
    47  			u.Pfv("%s\n", color.HiGreenString("%s", result.Output))
    48  
    49  		default:
    50  			defaultTimeout, _ := strconv.Atoi(ConfigRuntime().Timeout)
    51  			timeout := func() time.Duration {
    52  				var t int
    53  				if f.Timeout == 0 {
    54  					t = defaultTimeout
    55  				} else {
    56  					t = f.Timeout
    57  					u.LogWarn("explicit timeout:", u.Spf("%d milli seconds", t))
    58  				}
    59  				return time.Duration(t)
    60  			}()
    61  
    62  			ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Millisecond)
    63  			defer cancel()
    64  
    65  			cmdExec := exec.CommandContext(ctx, u.MainConfig.ShellType, "-c", cmd)
    66  
    67  			func() {
    68  				//inject the envvars
    69  				cmdExec.Env = os.Environ()
    70  				envvarObjMap := f.Vars.GetPrefixMatched("envVar_")
    71  				for k, v := range *envvarObjMap {
    72  					cmdExec.Env = append(cmdExec.Env, u.Spf("%s=%s", k, v.(string)))
    73  				}
    74  			}()
    75  
    76  			cmdExec.Stdin = os.Stdin
    77  
    78  			stdout, err := cmdExec.StdoutPipe()
    79  			if err != nil {
    80  				u.LogError("stdout pipe", err)
    81  			}
    82  
    83  			stderr, stderrErr := cmdExec.StderrPipe()
    84  			if err != nil {
    85  				u.LogError("stderr pipe", err)
    86  			}
    87  
    88  			if err = cmdExec.Start(); err != nil {
    89  				u.LogError("exec started", err)
    90  			}
    91  
    92  			u.PlnInfo("-")
    93  			outputResult := asyncStdReader("stdout", stdout, err, color.HiGreenString, idx1)
    94  			stdErrorResult := asyncStdReader("stderr", stderr, stderrErr, color.HiRedString, idx1)
    95  			u.PlnInfo("\n-")
    96  			err = cmdExec.Wait()
    97  
    98  			if ctx.Err() == context.DeadlineExceeded {
    99  				u.LogWarn("timeout", "shell execution timed out")
   100  			}
   101  
   102  			if err != nil {
   103  				if exitError, ok := err.(*exec.ExitError); ok {
   104  					result.Code = exitError.ExitCode()
   105  					if len(stdErrorResult) > 0 {
   106  						result.ErrMsg = stdErrorResult
   107  					} else {
   108  						result.ErrMsg = err.Error()
   109  					}
   110  				}
   111  			} else {
   112  				result.Code = 0
   113  			}
   114  
   115  			result.Output = strings.TrimSpace(outputResult)
   116  			f.Result = result
   117  		}
   118  	}
   119  }
   120  
   121  func asyncStdReader(readtype string, ch io.ReadCloser, err error, colorfunc func(format string, a ...interface{}) string, idx1 int) string {
   122  	var result *bytes.Buffer = bytes.NewBufferString("")
   123  	buff := make([]byte, 5120)
   124  	var n int
   125  	for err == nil {
   126  		n, err = ch.Read(buff)
   127  		if n > 0 {
   128  			if !(u.Contains(*StepRuntime().Flags, FLAG_SILENT) && readtype == "stdout") {
   129  				if u.Contains(*StepRuntime().Flags, u.Spf("%s-%d", FLAG_SILENT, idx1)) && readtype == "stdout" {
   130  				} else {
   131  					u.Pfv("%s", colorfunc("%s", string(buff[:n])))
   132  				}
   133  			}
   134  			result.Write(buff[:n])
   135  		}
   136  	}
   137  	return result.String()
   138  }
   139  
   140  type ShellFuncAction struct {
   141  	Do      interface{}
   142  	Vars    *core.Cache
   143  	Cmds    []string
   144  	Result  u.ExecResult
   145  	Timeout int
   146  }
   147  
   148  //adapt the abstract step.Do to concrete ShellFuncAction Cmds
   149  func (f *ShellFuncAction) Adapt() {
   150  	var cmd string
   151  	var cmds []string
   152  	f.Timeout = StepRuntime().Timeout
   153  
   154  	switch f.Do.(type) {
   155  	case string:
   156  		cmd = f.Do.(string)
   157  		cmds = append(cmds, cmd)
   158  
   159  	case []interface{}:
   160  		err := ms.Decode(f.Do, &cmds)
   161  		u.LogError("shell adapter", err)
   162  
   163  	default:
   164  		u.LogWarn("shell", "Not implemented or void for no action!")
   165  	}
   166  	f.Cmds = cmds
   167  }
   168  
   169  func (f *ShellFuncAction) Exec() {
   170  	for idx, tcmd := range f.Cmds {
   171  		u.Pfv("cmd(%2d):\n", idx+1)
   172  		u.Pvv(tcmd)
   173  		cleansed := func() string {
   174  			re := regexp.MustCompile(`{{.*\.secure_.*}}`)
   175  			return re.ReplaceAllString(tcmd, `SECURE_SENSITIVE_INFO_MASKED`)
   176  		}()
   177  		cmd := Render(tcmd, f.Vars)
   178  		cleansedCmd := Render(cleansed, f.Vars)
   179  		u.Pfvvvv("cmd=>:\n%s\n", color.HiBlueString("%s", cleansedCmd))
   180  		runCmd(f, cmd, idx+1)
   181  		u.SubStepStatus("..", f.Result.Code)
   182  		u.Dvvvvv(f.Result)
   183  	}
   184  
   185  	StepRuntime().Result = &f.Result
   186  }