actions: add a generic hook to define external commands and HTTP URL

We can only define a single hook now and it can be an HTTP notification
or an external command, not both
This commit is contained in:
Nicola Murino
2020-05-24 15:29:39 +02:00
parent 760cc9ba5a
commit c27e3ef436
8 changed files with 130 additions and 71 deletions

View File

@@ -7,11 +7,13 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
@@ -66,9 +68,11 @@ var (
setstatMode int
supportedSSHCommands = []string{"scp", "md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum", "cd", "pwd",
"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
defaultSSHCommands = []string{"md5sum", "sha1sum", "cd", "pwd", "scp"}
sshHashCommands = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"}
systemCommands = []string{"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
defaultSSHCommands = []string{"md5sum", "sha1sum", "cd", "pwd", "scp"}
sshHashCommands = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"}
systemCommands = []string{"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
errUnconfiguredAction = errors.New("no hook is configured for this action")
errNoHook = errors.New("unable to execute action, no hook defined")
)
type connectionTransfer struct {
@@ -92,10 +96,12 @@ type ActiveQuotaScan struct {
type Actions struct {
// Valid values are download, upload, delete, rename, ssh_cmd. Empty slice to disable
ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"`
// Absolute path to the command to execute, empty to disable
// Deprecated: please use Hook
Command string `json:"command" mapstructure:"command"`
// The URL to notify using an HTTP GET, empty to disable
// Deprecated: please use Hook
HTTPNotificationURL string `json:"http_notification_url" mapstructure:"http_notification_url"`
// Absolute path to an external program or an HTTP URL
Hook string `json:"hook" mapstructure:"hook"`
}
// ConnectionStatus status for an active connection
@@ -474,38 +480,36 @@ func isAtomicUploadEnabled() bool {
}
func executeNotificationCommand(a actionNotification) error {
if !filepath.IsAbs(actions.Hook) {
err := fmt.Errorf("invalid notification command %#v", actions.Hook)
logger.Warn(logSender, "", "unable to execute notification command: %v", err)
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, actions.Command, a.Action, a.Username, a.Path, a.TargetPath, a.SSHCmd)
cmd := exec.CommandContext(ctx, actions.Hook, a.Action, a.Username, a.Path, a.TargetPath, a.SSHCmd)
cmd.Env = append(os.Environ(), a.AsEnvVars()...)
startTime := time.Now()
err := cmd.Run()
logger.Debug(logSender, "", "executed command %#v with arguments: %#v, %#v, %#v, %#v, %#v, elapsed: %v, error: %v",
actions.Command, a.Action, a.Username, a.Path, a.TargetPath, a.SSHCmd, time.Since(startTime), err)
actions.Hook, a.Action, a.Username, a.Path, a.TargetPath, a.SSHCmd, time.Since(startTime), err)
return err
}
// executed in a goroutine
func executeAction(a actionNotification) error {
if !utils.IsStringInSlice(a.Action, actions.ExecuteOn) {
return nil
return errUnconfiguredAction
}
var err error
if len(actions.Command) > 0 && filepath.IsAbs(actions.Command) {
// we are in a goroutine but if we have to send an HTTP notification we don't want to wait for the
// end of the command
if len(actions.HTTPNotificationURL) > 0 {
go executeNotificationCommand(a) //nolint:errcheck
} else {
err = executeNotificationCommand(a) //nolint:errcheck
}
if len(actions.Hook) == 0 {
logger.Warn(logSender, "", "Unable to send notification, no hook is defined")
return errNoHook
}
if len(actions.HTTPNotificationURL) > 0 {
if strings.HasPrefix(actions.Hook, "http") {
var url *url.URL
url, err = url.Parse(actions.HTTPNotificationURL)
url, err := url.Parse(actions.Hook)
if err != nil {
logger.Warn(logSender, "", "Invalid http_notification_url %#v for operation %#v: %v", actions.HTTPNotificationURL,
a.Action, err)
logger.Warn(logSender, "", "Invalid hook %#v for operation %#v: %v", actions.Hook, a.Action, err)
return err
}
startTime := time.Now()
@@ -518,6 +522,7 @@ func executeAction(a actionNotification) error {
}
logger.Debug(logSender, "", "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
a.Action, url.String(), respCode, time.Since(startTime), err)
return err
}
return err
return executeNotificationCommand(a)
}