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  }