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 }