github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/utils/shell.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 utils 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "io" 15 "mvdan.cc/sh/v3/expand" 16 "mvdan.cc/sh/v3/interp" 17 "mvdan.cc/sh/v3/syntax" 18 "os" 19 "strings" 20 ) 21 22 type ExecResult struct { 23 Cmd string 24 Code int 25 Output string 26 ErrMsg string 27 } 28 29 type CmdOpts struct { 30 Command string 31 Dir string 32 Env []string 33 Stdin io.Reader 34 Stdout io.Writer 35 Stderr io.Writer 36 } 37 38 func getEnvs(envs *map[string]string) []string { 39 environ := os.Environ() 40 for k, v := range *envs { 41 environ = append(environ, fmt.Sprintf("%s=%s", k, v)) 42 } 43 return environ 44 } 45 46 func RunCmd(cmd string, dir string, envs *map[string]string) ExecResult { 47 stdOut := bytes.NewBufferString("") 48 stdErr := bytes.NewBufferString("") 49 stdin := os.Stdin 50 51 err := runCmdWithOps(&CmdOpts{ 52 Command: cmd, 53 Dir: dir, 54 Env: getEnvs(envs), 55 Stdin: stdin, 56 Stdout: stdOut, 57 Stderr: stdErr, 58 }) 59 var result ExecResult 60 var errored bool = false 61 if statusCode, ok := interp.IsExitStatus(err); ok { 62 errored = true 63 result.Code = int(statusCode) 64 result.ErrMsg = stdErr.String() 65 } 66 67 if !errored { 68 result.Code = 0 69 result.Output = stdOut.String() 70 } 71 72 return result 73 } 74 75 func runCmdWithOps(opts *CmdOpts) error { 76 p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "") 77 if err != nil { 78 return err 79 } 80 81 environ := opts.Env 82 if len(environ) == 0 { 83 environ = os.Environ() 84 } 85 86 r, err := interp.New( 87 interp.Dir(opts.Dir), 88 interp.Env(expand.ListEnviron(environ...)), 89 interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr), 90 ) 91 if err != nil { 92 return err 93 } 94 return r.Run(context.Background(), p) 95 } 96 97 func RunSimpleCmd(dir string, command string) error { 98 if dir != "" { 99 if _, err := os.Stat(dir); !os.IsNotExist(err) { 100 } else { 101 LogErrorAndPanic("check dir existence", err, "exec path does not exist") 102 } 103 } 104 105 r, err := interp.New( 106 interp.StdIO(os.Stdin, os.Stdout, os.Stderr), 107 interp.Dir(dir), 108 ) 109 if err != nil { 110 fmt.Println("error: init terminal errored", err) 111 } 112 113 if command != "" { 114 err = runSimple(r, strings.NewReader(command), "") 115 } 116 117 if err != nil { 118 LogErrorAndContinue("shell exec failed", err, "please exam the error and fix the problem and retry again") 119 } 120 121 return err 122 123 } 124 125 func runSimple(r *interp.Runner, reader io.Reader, name string) error { 126 prog, err := syntax.NewParser().Parse(reader, name) 127 if err != nil { 128 return err 129 } 130 r.Reset() 131 ctx := context.Background() 132 return r.Run(ctx, prog) 133 }