// Copyright 2023 schukai GmbH // SPDX-License-Identifier: AGPL-3.0 package jobqueue import ( "context" "fmt" "os" "os/exec" "strings" "sync" ) func NewShellRunnableFromMap(data map[string]interface{}) (*ShellRunnable, error) { scriptPath, _ := data["scriptpath"].(string) script, _ := data["script"].(string) if scriptPath != "" && script != "" { return nil, fmt.Errorf("%w: ScriptPath and Script are mutually exclusive", ErrInvalidData) } if scriptPath == "" && script == "" { return nil, fmt.Errorf("%w: ScriptPath or Script is required", ErrInvalidData) } return &ShellRunnable{ ScriptPath: scriptPath, Script: script, }, nil } // ShellResult is a result of a shell script type ShellResult struct { Output string Error string ExitCode int } func (s *ShellResult) GetResult() string { return s.Output } func (s *ShellResult) GetError() (string, int) { if s.ExitCode != 0 { return s.Error, s.ExitCode } return "", 0 } type ShellRunnable struct { ScriptPath string Script string mu sync.Mutex } func (s *ShellRunnable) Run(ctx context.Context) (RunResult[ShellResult], error) { scriptPath := s.ScriptPath if s.Script != "" { // write to temp tmp, err := os.CreateTemp("", "script-*.sh") if err != nil { Error("Failed to create temp file: %v", err) return RunResult[ShellResult]{ Status: ResultStatusFailed, Data: ShellResult{ Output: "", ExitCode: DefaultErrorExitCode, Error: fmt.Errorf("%w: %v", ErrFailedToCreateTempFile, err).Error(), }, }, err } scriptPath = tmp.Name() defer func() { _ = os.Remove(scriptPath) }() _, err = tmp.WriteString(s.Script) defer func() { _ = tmp.Close() }() if err != nil { Error("Failed to write temp file: %v", err) return RunResult[ShellResult]{ Status: ResultStatusFailed, Data: ShellResult{ Output: "", ExitCode: DefaultErrorExitCode, Error: fmt.Errorf("%w: %v", ErrFailedToWriteTempFile, err).Error(), }, }, err } } // #nosec cmd := exec.CommandContext(ctx, "sh", scriptPath) output, err := cmd.Output() var stderr []byte if err != nil { Error("Failed to run script: %v", err) stderr = err.(*exec.ExitError).Stderr } exitCode := SuccessExitCode if err != nil { if exitError, ok := err.(*exec.ExitError); ok { exitCode = exitError.ExitCode() } Error("Failed to run script: %v", err) return RunResult[ShellResult]{ Status: ResultStatusFailed, Data: ShellResult{ Output: string(output), ExitCode: exitCode, Error: string(stderr), }, }, err } return RunResult[ShellResult]{ Status: ResultStatusSuccess, Data: ShellResult{ Output: strings.TrimSpace(string(output)), ExitCode: exitCode, Error: string(stderr), }, }, nil } func (s *ShellRunnable) GetType() string { return "shell" } func (s *ShellRunnable) GetPersistence() RunnableImport { s.mu.Lock() defer s.mu.Unlock() data := JSONMap{ "scriptPath": s.ScriptPath, "script": s.Script, } return RunnableImport{ Type: s.GetType(), Data: data, } }