package common import ( "fmt" "io" "os" "os/exec" "path/filepath" "strings" "github.com/codeskyblue/go-sh" ) // ShellCmd represents a shell command to be run for dokku type ShellCmd struct { Env map[string]string Command *exec.Cmd CommandString string Args []string ShowOutput bool ExitError *exec.ExitError } // NewShellCmd returns a new ShellCmd struct // // Deprecated: use CallExecCommand instead func NewShellCmd(command string) *ShellCmd { items := strings.Split(command, " ") cmd := items[0] args := items[1:] return NewShellCmdWithArgs(cmd, args...) } // NewShellCmdWithArgs returns a new ShellCmd struct // // Deprecated: use CallExecCommand instead func NewShellCmdWithArgs(cmd string, args ...string) *ShellCmd { commandString := strings.Join(append([]string{cmd}, args...), " ") return &ShellCmd{ Command: exec.Command(cmd, args...), CommandString: commandString, Args: args, ShowOutput: true, } } func (sc *ShellCmd) setup() { env := os.Environ() for k, v := range sc.Env { env = append(env, fmt.Sprintf("%s=%s", k, v)) } sc.Command.Env = env if sc.ShowOutput { sc.Command.Stdout = os.Stdout sc.Command.Stderr = os.Stderr } } // Execute is a lightweight wrapper around exec.Command func (sc *ShellCmd) Execute() bool { sc.setup() if err := sc.Command.Run(); err != nil { exitError, ok := err.(*exec.ExitError) if ok { sc.ExitError = exitError } return false } return true } // Start is a wrapper around exec.Command.Start() func (sc *ShellCmd) Start() error { sc.setup() return sc.Command.Start() } // Output is a lightweight wrapper around exec.Command.Output() func (sc *ShellCmd) Output() ([]byte, error) { sc.setup() return sc.Command.Output() } // CombinedOutput is a lightweight wrapper around exec.Command.CombinedOutput() func (sc *ShellCmd) CombinedOutput() ([]byte, error) { sc.setup() return sc.Command.CombinedOutput() } // PlugnTrigger fire the given plugn trigger with the given args // // Deprecated: use CallPlugnTrigger instead func PlugnTrigger(triggerName string, args ...string) error { LogDebug(fmt.Sprintf("plugn trigger %s %v", triggerName, args)) return PlugnTriggerSetup(triggerName, args...).Run() } // PlugnTriggerOutput fire the given plugn trigger with the given args // // Deprecated: use CallPlugnTrigger with CaptureOutput=true instead func PlugnTriggerOutput(triggerName string, args ...string) ([]byte, error) { LogDebug(fmt.Sprintf("plugn trigger %s %v", triggerName, args)) rE, wE, _ := os.Pipe() rO, wO, _ := os.Pipe() session := PlugnTriggerSetup(triggerName, args...) session.Stderr = wE session.Stdout = wO err := session.Run() wE.Close() wO.Close() readStderr, _ := io.ReadAll(rE) readStdout, _ := io.ReadAll(rO) stderr := string(readStderr[:]) if err != nil { err = fmt.Errorf(stderr) } if os.Getenv("DOKKU_TRACE") == "1" { for _, line := range strings.Split(stderr, "\n") { LogDebug(fmt.Sprintf("plugn trigger %s stderr: %s", triggerName, line)) } for _, line := range strings.Split(string(readStdout[:]), "\n") { LogDebug(fmt.Sprintf("plugn trigger %s stdout: %s", triggerName, line)) } } return readStdout, err } // PlugnTriggerOutputAsString fires the given plugn trigger with the given args and returns the string contents instead of bytes // // Deprecated: use CallPlugnTrigger with CaptureOutput=true instead func PlugnTriggerOutputAsString(triggerName string, args ...string) (string, error) { b, err := PlugnTriggerOutput(triggerName, args...) return strings.TrimSpace(string(b[:])), err } // PlugnTriggerSetup sets up a plugn trigger call // // Deprecated: use CallPlugnTrigger instead func PlugnTriggerSetup(triggerName string, args ...string) *sh.Session { shellArgs := make([]interface{}, len(args)+2) shellArgs[0] = "trigger" shellArgs[1] = triggerName for i, arg := range args { shellArgs[i+2] = arg } return sh.Command("plugn", shellArgs...) } // PlugnTriggerExists returns whether a plugin trigger exists (ignoring the existence of any within the 20_events plugin) func PlugnTriggerExists(triggerName string) bool { pluginPath := MustGetEnv("PLUGIN_PATH") pluginPathPrefix := filepath.Join(pluginPath, "enabled") glob := filepath.Join(pluginPathPrefix, "*", triggerName) exists := false files, _ := filepath.Glob(glob) for _, file := range files { plugin := strings.Trim(strings.TrimPrefix(strings.TrimSuffix(file, "/"+triggerName), pluginPathPrefix), "/") if plugin != "20_events" { exists = true break } } return exists }