eventmanager placeholders: add StatusString and ErrorString

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-08-29 19:03:31 +02:00
parent 37d98ca290
commit 56bf51277c
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
9 changed files with 285 additions and 150 deletions

View file

@ -23,6 +23,8 @@ The following placeholders are supported:
- `{{Name}}`. Username, folder name or admin username for provider actions.
- `{{Event}}`. Event name, for example `upload`, `download` for filesystem events or `add`, `update` for provider events.
- `{{Status}}`. Status for `upload`, `download` and `ssh_cmd` events. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error.
- `{{StatusString}}`. Status as string. Possible values "OK", "KO".
- `{{ErrorString}}`. Error details. Replaced with an empty string if no errors occur.
- `{{VirtualPath}}`. Path seen by SFTPGo users, for example `/adir/afile.txt`.
- `{{FsPath}}`. Full filesystem path, for example `/user/homedir/adir/afile.txt` or `C:/data/user/homedir/adir/afile.txt` on Windows.
- `{{ObjectName}}`. File/directory name, for example `afile.txt` or provider object name.
@ -51,7 +53,7 @@ Actions such as user quota reset, transfer quota reset, data retention check, fo
Actions are executed in a sequential order except for sync actions that are executed before the others. For each action associated to a rule you can define the following settings:
- `Stop on failure`, the next action will not be executed if the current one fails.
- `Failure action`, this action will be executed only if at least another one fails.
- `Failure action`, this action will be executed only if at least another one fails. :warning: Please note that a failure action isn't executed if the event fails, for example if a download fails the main action is executed. The failure action is executed only if one of the non-failure actions associated to a rule fails.
- `Execute sync`, for upload events, you can execute the action synchronously. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your action have completed its execution. If your acion takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection.
If you are running multiple SFTPGo instances connected to the same data provider, you can choose whether to allow simultaneous execution for scheduled actions.

4
go.mod
View file

@ -50,7 +50,7 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5
github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.27.0
github.com/rs/zerolog v1.28.0
github.com/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657
github.com/shirou/gopsutil/v3 v3.22.7
github.com/spf13/afero v1.9.2
@ -155,7 +155,7 @@ require (
golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
google.golang.org/genproto v0.0.0-20220829144015-23454907ede3 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

9
go.sum
View file

@ -701,13 +701,12 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5 h1:7PcjxKTsfGXpTMiTNNa1VllbsYSZJN5nhvVEWQMdX8Y=
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -1221,8 +1220,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220829144015-23454907ede3 h1:4wwmycAWg7WUIFWgzxP6Wumy2GBLxmATgkhgpFnJl2U=
google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -568,14 +568,14 @@ func (c *Configuration) notifyCertificateRenewal(domain string, err error) {
}
params := common.EventParams{
Name: domain,
Event: "Certificate renewal",
Timestamp: time.Now().UnixNano(),
}
if err != nil {
params.Status = 2
params.Event = "Certificate renewal failed"
params.AddError(err)
} else {
params.Status = 1
params.Event = "Successful certificate renewal"
}
common.HandleCertificateEvent(params)
}

View file

@ -122,7 +122,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
}
var errRes error
if hasRules {
errRes = eventManager.handleFsEvent(EventParams{
params := EventParams{
Name: notification.Username,
Event: notification.Action,
Status: notification.Status,
@ -136,7 +136,11 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
IP: notification.IP,
Timestamp: notification.Timestamp,
Object: nil,
})
}
if err != nil {
params.AddError(fmt.Errorf("%q failed: %w", params.Event, err))
}
errRes = eventManager.handleFsEvent(params)
}
if hasHook {
if util.Contains(Config.Actions.ExecuteSync, operation) {

View file

@ -406,21 +406,50 @@ func (r *eventRulesContainer) handleCertificateEvent(params EventParams) {
// EventParams defines the supported event parameters
type EventParams struct {
Name string
Event string
Status int
VirtualPath string
FsPath string
VirtualTargetPath string
FsTargetPath string
ObjectName string
ObjectType string
FileSize int64
Protocol string
IP string
Timestamp int64
Object plugin.Renderer
sender string
Name string
Event string
Status int
VirtualPath string
FsPath string
VirtualTargetPath string
FsTargetPath string
ObjectName string
ObjectType string
FileSize int64
Protocol string
IP string
Timestamp int64
Object plugin.Renderer
sender string
updateStatusFromError bool
errors []string
}
func (p *EventParams) getACopy() *EventParams {
params := *p
params.errors = make([]string, len(p.errors))
copy(params.errors, p.errors)
return &params
}
// AddError adds a new error to the event params and update the status if needed
func (p *EventParams) AddError(err error) {
if err == nil {
return
}
if p.updateStatusFromError && p.Status == 1 {
p.Status = 2
}
p.errors = append(p.errors, err.Error())
}
func (p *EventParams) getStatusString() string {
switch p.Status {
case 1:
return "OK"
default:
return "KO"
}
}
// getUsers returns users with group settings not applied
@ -469,11 +498,18 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
"{{Protocol}}", p.Protocol,
"{{IP}}", p.IP,
"{{Timestamp}}", fmt.Sprintf("%d", p.Timestamp),
"{{StatusString}}", p.getStatusString(),
}
if len(p.errors) > 0 {
replacements = append(replacements, "{{ErrorString}}", strings.Join(p.errors, ", "))
} else {
replacements = append(replacements, "{{ErrorString}}", "")
}
replacements = append(replacements, "{{ObjectData}}", "")
if addObjectData {
data, err := p.Object.RenderAsJSON(p.Event != operationDelete)
if err == nil {
replacements = append(replacements, "{{ObjectData}}", string(data))
replacements[len(replacements)-1] = string(data)
}
}
return replacements
@ -516,7 +552,7 @@ func getMailAttachments(user dataprovider.User, attachments []string, replacer *
err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting email attachments, unable to check root fs for user %q: %w", user.Username, err)
}
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
totalSize := int64(0)
@ -596,7 +632,7 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s
return c.Endpoint, nil
}
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params EventParams) error {
func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventParams) error {
if !c.Password.IsEmpty() {
if err := c.Password.TryDecrypt(); err != nil {
return fmt.Errorf("unable to decrypt password: %w", err)
@ -653,7 +689,7 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params EventPar
return nil
}
func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params EventParams) error {
func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params *EventParams) error {
envVars := make([]string, 0, len(c.EnvVars))
addObjectData := false
if params.Object != nil {
@ -686,7 +722,7 @@ func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params Ev
return err
}
func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params EventParams) error {
func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *EventParams) error {
addObjectData := false
if params.Object != nil {
if strings.Contains(c.Body, "{{ObjectData}}") {
@ -748,7 +784,7 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck
if err != nil {
return err
return fmt.Errorf("delete error, unable to check root fs for user %q: %w", user.Username, err)
}
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range deletes {
@ -775,7 +811,7 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
}
func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams,
conditions dataprovider.ConditionOptions, params *EventParams,
) error {
users, err := params.getUsers()
if err != nil {
@ -792,6 +828,7 @@ func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer,
}
executed++
if err = executeDeleteFsActionForUser(deletes, replacer, user); err != nil {
params.AddError(err)
failures = append(failures, user.Username)
continue
}
@ -815,7 +852,7 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck
if err != nil {
return err
return fmt.Errorf("mkdir error, unable to check root fs for user %q: %w", user.Username, err)
}
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range dirs {
@ -832,7 +869,7 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
}
func executeMkdirFsRuleAction(dirs []string, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams,
conditions dataprovider.ConditionOptions, params *EventParams,
) error {
users, err := params.getUsers()
if err != nil {
@ -874,7 +911,7 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck
if err != nil {
return err
return fmt.Errorf("rename error, unable to check root fs for user %q: %w", user.Username, err)
}
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range renames {
@ -899,7 +936,7 @@ func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck
if err != nil {
return err
return fmt.Errorf("existence check error, unable to check root fs for user %q: %w", user.Username, err)
}
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range exist {
@ -913,7 +950,7 @@ func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
}
func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams,
conditions dataprovider.ConditionOptions, params *EventParams,
) error {
users, err := params.getUsers()
if err != nil {
@ -931,6 +968,7 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
executed++
if err = executeRenameFsActionForUser(renames, replacer, user); err != nil {
failures = append(failures, user.Username)
params.AddError(err)
continue
}
}
@ -945,7 +983,7 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
}
func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions,
params EventParams,
params *EventParams,
) error {
users, err := params.getUsers()
if err != nil {
@ -963,6 +1001,7 @@ func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, condit
executed++
if err = executeExistFsActionForUser(exist, replacer, user); err != nil {
failures = append(failures, user.Username)
params.AddError(err)
continue
}
}
@ -977,7 +1016,7 @@ func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, condit
}
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions,
params EventParams,
params *EventParams,
) error {
addObjectData := false
replacements := params.getStringReplacements(addObjectData)
@ -1003,25 +1042,25 @@ func executeQuotaResetForUser(user dataprovider.User) error {
return err
}
if !QuotaScans.AddUserQuotaScan(user.Username) {
eventManagerLog(logger.LevelError, "another quota scan is already in progress for user %s", user.Username)
return fmt.Errorf("another quota scan is in progress for user %s", user.Username)
eventManagerLog(logger.LevelError, "another quota scan is already in progress for user %q", user.Username)
return fmt.Errorf("another quota scan is in progress for user %q", user.Username)
}
defer QuotaScans.RemoveUserQuotaScan(user.Username)
numFiles, size, err := user.ScanQuota()
if err != nil {
eventManagerLog(logger.LevelError, "error scanning quota for user %s: %v", user.Username, err)
return err
eventManagerLog(logger.LevelError, "error scanning quota for user %q: %v", user.Username, err)
return fmt.Errorf("error scanning quota for user %q: %w", user.Username, err)
}
err = dataprovider.UpdateUserQuota(&user, numFiles, size, true)
if err != nil {
eventManagerLog(logger.LevelError, "error updating quota for user %s: %v", user.Username, err)
return err
eventManagerLog(logger.LevelError, "error updating quota for user %q: %v", user.Username, err)
return fmt.Errorf("error updating quota for user %q: %w", user.Username, err)
}
return nil
}
func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error {
func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
users, err := params.getUsers()
if err != nil {
return fmt.Errorf("unable to get users: %w", err)
@ -1031,12 +1070,13 @@ func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions,
for _, user := range users {
// if sender is set, the conditions have already been evaluated
if params.sender == "" && !checkEventConditionPatterns(user.Username, conditions.Names) {
eventManagerLog(logger.LevelDebug, "skipping scheduled quota reset for user %s, name conditions don't match",
eventManagerLog(logger.LevelDebug, "skipping quota reset for user %q, name conditions don't match",
user.Username)
continue
}
executed++
if err = executeQuotaResetForUser(user); err != nil {
params.AddError(err)
failedResets = append(failedResets, user.Username)
continue
}
@ -1051,7 +1091,7 @@ func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions,
return nil
}
func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error {
func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
folders, err := params.getFolders()
if err != nil {
return fmt.Errorf("unable to get folders: %w", err)
@ -1066,7 +1106,8 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
continue
}
if !QuotaScans.AddVFolderQuotaScan(folder.Name) {
eventManagerLog(logger.LevelError, "another quota scan is already in progress for folder %s", folder.Name)
eventManagerLog(logger.LevelError, "another quota scan is already in progress for folder %q", folder.Name)
params.AddError(fmt.Errorf("another quota scan is already in progress for folder %q", folder.Name))
failedResets = append(failedResets, folder.Name)
continue
}
@ -1078,13 +1119,15 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
numFiles, size, err := f.ScanQuota()
QuotaScans.RemoveVFolderQuotaScan(folder.Name)
if err != nil {
eventManagerLog(logger.LevelError, "error scanning quota for folder %s: %v", folder.Name, err)
eventManagerLog(logger.LevelError, "error scanning quota for folder %q: %v", folder.Name, err)
params.AddError(fmt.Errorf("error scanning quota for folder %q: %w", folder.Name, err))
failedResets = append(failedResets, folder.Name)
continue
}
err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true)
if err != nil {
eventManagerLog(logger.LevelError, "error updating quota for folder %s: %v", folder.Name, err)
eventManagerLog(logger.LevelError, "error updating quota for folder %q: %v", folder.Name, err)
params.AddError(fmt.Errorf("error updating quota for folder %q: %w", folder.Name, err))
failedResets = append(failedResets, folder.Name)
}
}
@ -1098,7 +1141,7 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
return nil
}
func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error {
func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
users, err := params.getUsers()
if err != nil {
return fmt.Errorf("unable to get users: %w", err)
@ -1115,7 +1158,8 @@ func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOption
executed++
err = dataprovider.UpdateUserTransferQuota(&user, 0, 0, true)
if err != nil {
eventManagerLog(logger.LevelError, "error updating transfer quota for user %s: %v", user.Username, err)
eventManagerLog(logger.LevelError, "error updating transfer quota for user %q: %v", user.Username, err)
params.AddError(fmt.Errorf("error updating transfer quota for user %q: %w", user.Username, err))
failedResets = append(failedResets, user.Username)
}
}
@ -1140,18 +1184,18 @@ func executeDataRetentionCheckForUser(user dataprovider.User, folders []dataprov
}
c := RetentionChecks.Add(check, &user)
if c == nil {
eventManagerLog(logger.LevelError, "another retention check is already in progress for user %s", user.Username)
return fmt.Errorf("another retention check is in progress for user %s", user.Username)
eventManagerLog(logger.LevelError, "another retention check is already in progress for user %q", user.Username)
return fmt.Errorf("another retention check is in progress for user %q", user.Username)
}
if err := c.Start(); err != nil {
eventManagerLog(logger.LevelError, "error checking retention for user %s: %v", user.Username, err)
return err
eventManagerLog(logger.LevelError, "error checking retention for user %q: %v", user.Username, err)
return fmt.Errorf("error checking retention for user %q: %w", user.Username, err)
}
return nil
}
func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRetentionConfig,
conditions dataprovider.ConditionOptions, params EventParams,
conditions dataprovider.ConditionOptions, params *EventParams,
) error {
users, err := params.getUsers()
if err != nil {
@ -1169,6 +1213,7 @@ func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRete
executed++
if err = executeDataRetentionCheckForUser(user, config.Folders); err != nil {
failedChecks = append(failedChecks, user.Username)
params.AddError(err)
continue
}
}
@ -1182,29 +1227,37 @@ func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRete
return nil
}
func executeRuleAction(action dataprovider.BaseEventAction, params EventParams, conditions dataprovider.ConditionOptions) error {
func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams, conditions dataprovider.ConditionOptions) error {
var err error
switch action.Type {
case dataprovider.ActionTypeHTTP:
return executeHTTPRuleAction(action.Options.HTTPConfig, params)
err = executeHTTPRuleAction(action.Options.HTTPConfig, params)
case dataprovider.ActionTypeCommand:
return executeCommandRuleAction(action.Options.CmdConfig, params)
err = executeCommandRuleAction(action.Options.CmdConfig, params)
case dataprovider.ActionTypeEmail:
return executeEmailRuleAction(action.Options.EmailConfig, params)
err = executeEmailRuleAction(action.Options.EmailConfig, params)
case dataprovider.ActionTypeBackup:
return dataprovider.ExecuteBackup()
err = dataprovider.ExecuteBackup()
case dataprovider.ActionTypeUserQuotaReset:
return executeUsersQuotaResetRuleAction(conditions, params)
err = executeUsersQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeFolderQuotaReset:
return executeFoldersQuotaResetRuleAction(conditions, params)
err = executeFoldersQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeTransferQuotaReset:
return executeTransferQuotaResetRuleAction(conditions, params)
err = executeTransferQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeDataRetentionCheck:
return executeDataRetentionCheckRuleAction(action.Options.RetentionConfig, conditions, params)
err = executeDataRetentionCheckRuleAction(action.Options.RetentionConfig, conditions, params)
case dataprovider.ActionTypeFilesystem:
return executeFsRuleAction(action.Options.FsConfig, conditions, params)
err = executeFsRuleAction(action.Options.FsConfig, conditions, params)
default:
return fmt.Errorf("unsupported action type: %d", action.Type)
err = fmt.Errorf("unsupported action type: %d", action.Type)
}
if err != nil {
err = fmt.Errorf("action %q failed: %w", action.Name, err)
}
params.AddError(err)
return err
}
func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams) error {
@ -1212,10 +1265,11 @@ func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams)
for _, rule := range rules {
var failedActions []string
paramsCopy := params.getACopy()
for _, action := range rule.Actions {
if !action.Options.IsFailureAction && action.Options.ExecuteSync {
startTime := time.Now()
if err := executeRuleAction(action.BaseEventAction, params, rule.Conditions.Options); err != nil {
if err := executeRuleAction(action.BaseEventAction, paramsCopy, rule.Conditions.Options); err != nil {
eventManagerLog(logger.LevelError, "unable to execute sync action %q for rule %q, elapsed %s, err: %v",
action.Name, rule.Name, time.Since(startTime), err)
failedActions = append(failedActions, action.Name)
@ -1231,7 +1285,7 @@ func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams)
}
}
// execute async actions if any, including failure actions
go executeRuleAsyncActions(rule, params, failedActions)
go executeRuleAsyncActions(rule, paramsCopy, failedActions)
}
return errRes
@ -1242,11 +1296,11 @@ func executeAsyncRulesActions(rules []dataprovider.EventRule, params EventParams
defer eventManager.removeAsyncTask()
for _, rule := range rules {
executeRuleAsyncActions(rule, params, nil)
executeRuleAsyncActions(rule, params.getACopy(), nil)
}
}
func executeRuleAsyncActions(rule dataprovider.EventRule, params EventParams, failedActions []string) {
func executeRuleAsyncActions(rule dataprovider.EventRule, params *EventParams, failedActions []string) {
for _, action := range rule.Actions {
if !action.Options.IsFailureAction && !action.Options.ExecuteSync {
startTime := time.Now()
@ -1361,9 +1415,9 @@ func (j *eventCronJob) Run() {
}
}(task.Name)
executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{})
executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{Status: 1, updateStatusFromError: true})
} else {
executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{})
executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{Status: 1, updateStatusFromError: true})
}
eventManagerLog(logger.LevelDebug, "execution for scheduled rule %q finished", j.ruleName)
}

View file

@ -270,19 +270,19 @@ func TestEventManagerErrors(t *testing.T) {
_, err = params.getFolders()
assert.Error(t, err)
err = executeUsersQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{})
err = executeUsersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeFoldersQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{})
err = executeFoldersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{})
err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
err = executeExistFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
err = executeExistFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err)
groupName := "agroup"
@ -362,7 +362,7 @@ func TestEventManagerErrors(t *testing.T) {
},
},
}
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "username1",
@ -421,10 +421,10 @@ func TestEventRuleActions(t *testing.T) {
Name: actionName,
Type: dataprovider.ActionTypeBackup,
}
err := executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{})
err := executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
assert.NoError(t, err)
action.Type = -1
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{})
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
assert.Error(t, err)
action = dataprovider.BaseEventAction{
@ -454,12 +454,12 @@ func TestEventRuleActions(t *testing.T) {
},
}
action.Options.SetEmptySecretsIfNil()
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{})
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid endpoint")
}
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v", httpAddr)
params := EventParams{
params := &EventParams{
Name: "a",
Object: &dataprovider.User{
BaseUser: sdk.BaseUser{
@ -472,7 +472,7 @@ func TestEventRuleActions(t *testing.T) {
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) {
assert.Equal(t, err.Error(), "unexpected status code: 404")
assert.Contains(t, err.Error(), "unexpected status code: 404")
}
action.Options.HTTPConfig.Endpoint = "http://invalid:1234"
err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
@ -517,7 +517,7 @@ func TestEventRuleActions(t *testing.T) {
action = dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeUserQuotaReset,
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -530,7 +530,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(user1.GetHomeDir(), "file.txt"), []byte("user"), 0666)
assert.NoError(t, err)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -544,7 +544,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(4), userGet.UsedQuotaSize)
// simulate another quota scan in progress
assert.True(t, QuotaScans.AddUserQuotaScan(username1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -554,7 +554,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err)
assert.True(t, QuotaScans.RemoveUserQuotaScan(username1))
// non matching pattern
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "don't match",
@ -578,7 +578,7 @@ func TestEventRuleActions(t *testing.T) {
},
},
}
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -622,7 +622,7 @@ func TestEventRuleActions(t *testing.T) {
err = os.Chtimes(file4, timeBeforeRetention, timeBeforeRetention)
assert.NoError(t, err)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -637,7 +637,7 @@ func TestEventRuleActions(t *testing.T) {
// simulate another check in progress
c := RetentionChecks.Add(RetentionCheck{}, &user1)
assert.NotNil(t, c)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -647,7 +647,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err)
RetentionChecks.remove(user1.Username)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -667,7 +667,7 @@ func TestEventRuleActions(t *testing.T) {
},
},
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -677,7 +677,7 @@ func TestEventRuleActions(t *testing.T) {
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no existence check executed")
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -686,7 +686,7 @@ func TestEventRuleActions(t *testing.T) {
})
assert.NoError(t, err)
action.Options.FsConfig.Exist = []string{"/file1.txt", path.Join("/", retentionDir, "file2.txt")}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -702,7 +702,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err)
action.Type = dataprovider.ActionTypeTransferQuotaReset
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username1,
@ -715,7 +715,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer)
assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -737,7 +737,7 @@ func TestEventRuleActions(t *testing.T) {
},
},
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -753,7 +753,7 @@ func TestEventRuleActions(t *testing.T) {
Deletes: []string{"/dir1"},
},
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -769,7 +769,7 @@ func TestEventRuleActions(t *testing.T) {
Deletes: []string{"/dir1"},
},
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no match",
@ -802,7 +802,7 @@ func TestEventRuleActions(t *testing.T) {
action = dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeFolderQuotaReset,
}
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: foldername1,
@ -814,7 +814,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(folder1.MappedPath, "file.txt"), []byte("folder"), 0666)
assert.NoError(t, err)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: foldername1,
@ -828,7 +828,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(6), folderGet.UsedQuotaSize)
// simulate another quota scan in progress
assert.True(t, QuotaScans.AddVFolderQuotaScan(foldername1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: foldername1,
@ -838,7 +838,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err)
assert.True(t, QuotaScans.RemoveVFolderQuotaScan(foldername1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: "no folder match",
@ -920,7 +920,7 @@ func TestGetFileContent(t *testing.T) {
}
func TestFilesystemActionErrors(t *testing.T) {
err := executeFsRuleAction(dataprovider.EventActionFilesystemConfig{}, dataprovider.ConditionOptions{}, EventParams{})
err := executeFsRuleAction(dataprovider.EventActionFilesystemConfig{}, dataprovider.ConditionOptions{}, &EventParams{})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported filesystem action")
}
@ -950,7 +950,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Subject: "subject",
Body: "body",
Attachments: []string{"/file.txt"},
}, EventParams{
}, &EventParams{
sender: username,
})
assert.Error(t, err)
@ -973,7 +973,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Subject: "subject",
Body: "body",
Attachments: []string{"/file1.txt"},
}, EventParams{
}, &EventParams{
sender: username,
})
assert.Error(t, err)
@ -1008,7 +1008,7 @@ func TestFilesystemActionErrors(t *testing.T) {
},
},
},
}, EventParams{}, dataprovider.ConditionOptions{
}, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username,
@ -1045,7 +1045,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Deletes: []string{"/adir/sub/f.dat"},
},
},
}, EventParams{}, dataprovider.ConditionOptions{
}, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username,
@ -1071,7 +1071,7 @@ func TestFilesystemActionErrors(t *testing.T) {
MkDirs: []string{"/adir/sub/sub1"},
},
},
}, EventParams{}, dataprovider.ConditionOptions{
}, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username,
@ -1119,7 +1119,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeUserQuotaReset},
EventParams{}, dataprovider.ConditionOptions{
&EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username,
@ -1129,7 +1129,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
assert.Error(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeTransferQuotaReset},
EventParams{}, dataprovider.ConditionOptions{
&EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: username,
@ -1154,7 +1154,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
assert.NoError(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeFolderQuotaReset},
EventParams{}, dataprovider.ConditionOptions{
&EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{
{
Pattern: foldername,
@ -1242,3 +1242,37 @@ func TestScheduledActions(t *testing.T) {
assert.NoError(t, err)
stopEventScheduler()
}
func TestEventParamsCopy(t *testing.T) {
params := EventParams{
Name: "name",
Event: "event",
Status: 1,
errors: []string{"error1"},
}
paramsCopy := params.getACopy()
assert.Equal(t, params, *paramsCopy)
params.Name = "name mod"
paramsCopy.Event = "event mod"
paramsCopy.Status = 2
params.errors = append(params.errors, "error2")
paramsCopy.errors = append(paramsCopy.errors, "error3")
assert.Equal(t, []string{"error1", "error3"}, paramsCopy.errors)
assert.Equal(t, []string{"error1", "error2"}, params.errors)
assert.Equal(t, "name mod", params.Name)
assert.Equal(t, "name", paramsCopy.Name)
assert.Equal(t, "event", params.Event)
assert.Equal(t, "event mod", paramsCopy.Event)
assert.Equal(t, 1, params.Status)
assert.Equal(t, 2, paramsCopy.Status)
}
func TestEventParamsStatusFromError(t *testing.T) {
params := EventParams{Status: 1}
params.AddError(os.ErrNotExist)
assert.Equal(t, 1, params.Status)
params = EventParams{Status: 1, updateStatusFromError: true}
params.AddError(os.ErrNotExist)
assert.Equal(t, 2, params.Status)
}

View file

@ -19,6 +19,7 @@ import (
"bytes"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"math"
@ -3064,8 +3065,8 @@ func TestEventRule(t *testing.T) {
Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"test1@example.com", "test2@example.com"},
Subject: `New "{{Event}}" from "{{Name}}"`,
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}}",
Subject: `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}} {{ErrorString}}",
},
},
}
@ -3076,7 +3077,7 @@ func TestEventRule(t *testing.T) {
EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"failure@example.com"},
Subject: `Failed "{{Event}}" from "{{Name}}"`,
Body: "Fs path {{FsPath}}, protocol: {{Protocol}}, IP: {{IP}}",
Body: "Fs path {{FsPath}}, protocol: {{Protocol}}, IP: {{IP}} {{ErrorString}}",
},
},
}
@ -3187,6 +3188,7 @@ func TestEventRule(t *testing.T) {
uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
u := getTestUser()
u.DownloadDataTransfer = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
movedFileName := "moved.dat"
@ -3230,6 +3232,8 @@ func TestEventRule(t *testing.T) {
dirName := "subdir"
err = client.Mkdir(dirName)
assert.NoError(t, err)
err = client.Mkdir("subdir1")
assert.NoError(t, err)
// rule conditions match
lastReceivedEmail.reset()
err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
@ -3247,7 +3251,32 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: New "upload" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "upload" from "%s" status OK`, user.Username))
// test the failure action, we download a file that exceeds the transfer quota limit
err = writeSFTPFileNoCheck(path.Join("subdir1", testFileName), 1*1024*1024+65535, client)
assert.NoError(t, err)
lastReceivedEmail.reset()
f, err := client.Open(path.Join("subdir1", testFileName))
assert.NoError(t, err)
_, err = io.ReadAll(f)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
}
err = f.Close()
assert.Error(t, err)
assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != ""
}, 3000*time.Millisecond, 100*time.Millisecond)
email = lastReceivedEmail.get()
assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@example.com"))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s" status KO`, user.Username))
assert.Contains(t, email.Data, `"download" failed`)
assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error())
_, err = httpdtest.UpdateTransferQuotaUsage(user, "", http.StatusOK)
assert.NoError(t, err)
// remove the upload script to test the failure action
err = os.Remove(uploadScriptPath)
assert.NoError(t, err)
@ -3260,10 +3289,11 @@ func TestEventRule(t *testing.T) {
email = lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "failure@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`action %q failed`, action1.Name))
// now test the download rule
lastReceivedEmail.reset()
f, err := client.Open(movedFileName)
f, err = client.Open(movedFileName)
assert.NoError(t, err)
contents, err := io.ReadAll(f)
assert.NoError(t, err)
@ -3277,7 +3307,7 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
}
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@ -3291,7 +3321,7 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@example.com"))
assert.Contains(t, string(email.Data), `Subject: New "delete" from "admin"`)
assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`)
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
@ -3443,7 +3473,7 @@ func TestEventRuleProviderEvents(t *testing.T) {
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test3@example.com"))
assert.Contains(t, string(email.Data), `Subject: New "update" from "admin"`)
assert.Contains(t, email.Data, `Subject: New "update" from "admin"`)
}
// now delete the script to generate an error
lastReceivedEmail.reset()
@ -3458,8 +3488,8 @@ func TestEventRuleProviderEvents(t *testing.T) {
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "failure@example.com"))
assert.Contains(t, string(email.Data), `Subject: Failed "update" from "admin"`)
assert.Contains(t, string(email.Data), fmt.Sprintf("Object name: %s object type: folder", folder.Name))
assert.Contains(t, email.Data, `Subject: Failed "update" from "admin"`)
assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
lastReceivedEmail.reset()
// generate an error for the failure action
smtpCfg = smtp.Config{}
@ -3830,8 +3860,8 @@ func TestEventActionEmailAttachments(t *testing.T) {
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
assert.Contains(t, string(email.Data), "Content-Disposition: attachment")
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
assert.Contains(t, email.Data, "Content-Disposition: attachment")
}
}
@ -3929,7 +3959,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
lastReceivedEmail.reset()
// a new upload will not produce a new notification
err = writeSFTPFileNoCheck(testFileName+"_1", 32768, client)
@ -3952,7 +3982,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
email = lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
// download again
lastReceivedEmail.reset()
f, err = client.Open(testFileName)
@ -4001,8 +4031,8 @@ func TestEventRuleCertificate(t *testing.T) {
Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"test@example.com"},
Subject: `"{{Event}}"`,
Body: "Domain: {{Name}} Timestamp: {{Timestamp}}",
Subject: `"{{Event}} {{StatusString}}"`,
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
},
},
}
@ -4051,11 +4081,13 @@ func TestEventRuleCertificate(t *testing.T) {
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
assert.NoError(t, err)
renewalEvent := "Certificate renewal"
common.HandleCertificateEvent(common.EventParams{
Name: "example.com",
Timestamp: time.Now().UnixNano(),
Status: 1,
Event: "Successful certificate renewal",
Event: renewalEvent,
})
assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != ""
@ -4063,24 +4095,28 @@ func TestEventRuleCertificate(t *testing.T) {
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), `Subject: "Successful certificate renewal"`)
assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`)
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
lastReceivedEmail.reset()
common.HandleCertificateEvent(common.EventParams{
params := common.EventParams{
Name: "example.com",
Timestamp: time.Now().UnixNano(),
Status: 2,
Event: "Certificate renewal failed",
})
Event: renewalEvent,
}
errRenew := errors.New("generic renew error")
params.AddError(errRenew)
common.HandleCertificateEvent(params)
assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != ""
}, 3000*time.Millisecond, 100*time.Millisecond)
email = lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), `Subject: "Certificate renewal failed"`)
assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`)
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
assert.Contains(t, email.Data, errRenew.Error())
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
assert.NoError(t, err)
@ -4095,7 +4131,7 @@ func TestEventRuleCertificate(t *testing.T) {
Name: "example.com",
Timestamp: time.Now().UnixNano(),
Status: 1,
Event: "Successful certificate renewal",
Event: renewalEvent,
})
smtpCfg = smtp.Config{}
@ -4184,7 +4220,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
assert.NoError(t, err)
lastReceivedEmail.reset()
time.Sleep(300 * time.Millisecond)
assert.Empty(t, lastReceivedEmail.get().From, string(lastReceivedEmail.get().Data))
assert.Empty(t, lastReceivedEmail.get().From, lastReceivedEmail.get().Data)
for i := 0; i < 3; i++ {
user.Password = "wrong_pwd"
@ -4203,7 +4239,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test3@example.com"))
assert.True(t, util.Contains(email.To, "test4@example.com"))
assert.Contains(t, string(email.Data), `Subject: New "IP Blocked"`)
assert.Contains(t, email.Data, `Subject: New "IP Blocked"`)
err = dataprovider.DeleteEventRule(rule1.Name, "", "")
assert.NoError(t, err)
@ -5305,7 +5341,7 @@ type receivedEmail struct {
sync.RWMutex
From string
To []string
Data []byte
Data string
}
func (e *receivedEmail) set(from string, to []string, data []byte) {
@ -5314,7 +5350,7 @@ func (e *receivedEmail) set(from string, to []string, data []byte) {
e.From = from
e.To = to
e.Data = data
e.Data = strings.ReplaceAll(string(data), "=\r\n", "")
}
func (e *receivedEmail) reset() {
@ -5323,7 +5359,7 @@ func (e *receivedEmail) reset() {
e.From = ""
e.To = nil
e.Data = nil
e.Data = ""
}
func (e *receivedEmail) get() receivedEmail {

View file

@ -541,6 +541,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<p>
<span class="shortcut"><b>{{`{{Status}}`}}</b></span> => Status for "upload", "download" and "ssh_cmd" events. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error.
</p>
<p>
<span class="shortcut"><b>{{`{{StatusString}}`}}</b></span> => Status as string. Possible values "OK", "KO".
</p>
<p>
<span class="shortcut"><b>{{`{{ErrorString}}`}}</b></span> => Error details. Replaced with an empty string if no errors occur.
</p>
<p>
<span class="shortcut"><b>{{`{{VirtualPath}}`}}</b></span> => Path seen by SFTPGo users, for example "/adir/afile.txt".
</p>