package cli import ( "context" "errors" "io" "os/exec" "regexp" "strings" ) var re = regexp.MustCompile(`\w|\%|^(\+|,|\.|:|\/|\\|=|_|-|[a-z]|[A-Z]|[0-9]|\s)+$`) func validateCliArgument(arg string) bool { return re.Match([]byte(arg)) } func validateCliArguments(args []string) bool { for _, p := range args { if !validateCliArgument(p) { return false } } return true } func execNoCheck(ctx context.Context, command string, args []string) (string, string, error) { cmd := exec.CommandContext(ctx, command, args...) cmdStr := cmd.String() pStdout, err := cmd.StdoutPipe() if err != nil { return "", cmdStr, err } pStderr, err := cmd.StderrPipe() if err != nil { return "", cmdStr, err } err = cmd.Start() if err != nil { return "", cmdStr, err } stdout, _ := io.ReadAll(pStdout) stderr, _ := io.ReadAll(pStderr) err = cmd.Wait() if err != nil { return "", cmdStr, errors.New(err.Error() + " | " + string(stderr) + " | " + string(stdout)) } return string(stdout), cmdStr, nil } type Pipe struct { Command string SuffixSymbol string Args []string } func (p *Pipe) IsSanatory() bool { return validateCliArguments(p.Args) && validateCliArgument(p.Command) } func (p *Pipe) Execute(ctx context.Context) (string, string, error) { if !p.IsSanatory() { return "", "", errors.New("failed to execute cli command: " + p.Command + " " + strings.Join(p.Args, " ") + " some arguments are not allowed") } else { return execNoCheck(ctx, p.Command, p.Args) } } // ExecuteCombined runs the command and returns combined stdout+stderr output. // Useful when a tool writes useful output to either stream. func (p *Pipe) ExecuteCombined(ctx context.Context) (string, string, error) { if !p.IsSanatory() { return "", "", errors.New("failed to execute cli command: " + p.Command + " " + strings.Join(p.Args, " ") + " some arguments are not allowed") } cmd := exec.CommandContext(ctx, p.Command, p.Args...) cmdStr := cmd.String() out, err := cmd.CombinedOutput() if err != nil { return "", cmdStr, errors.New(err.Error() + " | " + string(out)) } return string(out), cmdStr, nil } func ExecuteCLIPipeLine(ctx context.Context, pipes []Pipe) (string, string, error) { pipeLen := len(pipes) if pipeLen == 0 { return "", "", errors.New("failed to execute cli command: no pipes provided") } argLen := 0 for _, p := range pipes { if !p.IsSanatory() { return "", "", errors.New("failed to execute cli command: " + p.Command + " " + strings.Join(p.Args, " ") + " some arguments are not allowed") } argLen += len(p.Args) + 1 } command := "/bin/sh" var args []string = []string{} args = append(args, "-c") pipeLen -= 1 cmdArg := "" for i, p := range pipes { cmdArg += p.Command + " " + strings.Join(p.Args, " ") if i != pipeLen { if p.SuffixSymbol != "" { cmdArg += " " + p.SuffixSymbol + " " } else { cmdArg += " | " } } } args = append(args, cmdArg) return execNoCheck(ctx, command, args) }