Jelajahi Sumber

Notification support (#183)

* feature: #158 shellAfterComplete support for notifications

* fmt: reduce cyclo
James Read 2 tahun lalu
induk
melakukan
8f6b384fe6

+ 5 - 3
config.yaml

@@ -10,15 +10,17 @@ logLevel: "INFO"
 
 # Actions (buttons) to show up on the WebUI:
 actions:
+- title: date
+  shell: date
+  popupOnStart: true
+
   # This will run a simple script that you create.
 - title: Run backup script
   shell: /opt/backupScript.sh
+  shellAfterCompleted: "apprise -t 'Notification: Backup script completed' -b 'The backup script completed with code {{ exitCode}}. The log is: \n {{ stdout }} '"
   maxConcurrent: 1
   icon: backup
 
-- title: date
-  shell: date
-
   # This will send 1 ping (-c 1)
   # Docs: https://docs.olivetin.app/action-ping.html
 - title: Ping host

+ 1 - 0
internal/config/config.go

@@ -7,6 +7,7 @@ type Action struct {
 	Title                  string
 	Icon                   string
 	Shell                  string
+	ShellAfterCompleted    string
 	CSS                    map[string]string `mapstructure:"omitempty"`
 	Timeout                int
 	Acls                   []string

+ 11 - 0
internal/config/config_helpers.go

@@ -13,6 +13,17 @@ func (cfg *Config) FindAction(actionTitle string) *Action {
 
 // FindArg will return an arg if there is a match on Name
 func (action *Action) FindArg(name string) *ActionArgument {
+	if name == "stdout" || name == "exitCode" {
+		return &ActionArgument{
+			Name: name,
+			Type: "very_dangerous_raw_string",
+		}
+	}
+
+	return action.findArg(name)
+}
+
+func (action *Action) findArg(name string) *ActionArgument {
 	for _, arg := range action.Arguments {
 		if arg.Name == name {
 			return &arg

+ 45 - 0
internal/executor/executor.go

@@ -83,6 +83,7 @@ func DefaultExecutor() *Executor {
 		stepParseArgs,
 		stepLogStart,
 		stepExec,
+		stepExecAfter,
 		stepLogFinish,
 	}
 
@@ -300,3 +301,47 @@ func stepExec(req *ExecutionRequest) bool {
 
 	return true
 }
+
+func stepExecAfter(req *ExecutionRequest) bool {
+	if req.action.ShellAfterCompleted == "" {
+		return true
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.action.Timeout)*time.Second)
+	defer cancel()
+
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+
+	args := map[string]string{
+		"stdout":   req.logEntry.Stdout,
+		"exitCode": fmt.Sprintf("%v", req.logEntry.ExitCode),
+	}
+
+	finalParsedCommand, _ := parseActionArguments(req.action.ShellAfterCompleted, args, req.action)
+
+	cmd := wrapCommandInShell(ctx, finalParsedCommand)
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	req.logEntry.StdoutBuffer, _ = cmd.StdoutPipe()
+	req.logEntry.StderrBuffer, _ = cmd.StderrPipe()
+
+	runerr := cmd.Start()
+
+	cmd.Wait()
+
+	req.logEntry.Stdout += "---\n" + stdout.String()
+	req.logEntry.Stderr += "---\n" + stderr.String()
+
+	if runerr != nil {
+		req.logEntry.Stderr = runerr.Error() + "\n\n" + req.logEntry.Stderr
+	}
+
+	if ctx.Err() == context.DeadlineExceeded {
+		req.logEntry.Stderr += "Your shellAfterCommand command timed out."
+	}
+
+	req.logEntry.Stdout += fmt.Sprintf("Your shellAfterCommand exited with code %v", cmd.ProcessState.ExitCode())
+
+	return true
+}