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. - `{{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. - `{{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. - `{{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`. - `{{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. - `{{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. - `{{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: 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. - `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. - `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. 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/robfig/cron/v3 v3.0.1
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5 github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5
github.com/rs/xid v1.4.0 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/sftpgo/sdk v0.1.2-0.20220828084006-f9e2fffac657
github.com/shirou/gopsutil/v3 v3.22.7 github.com/shirou/gopsutil/v3 v3.22.7
github.com/spf13/afero v1.9.2 github.com/spf13/afero v1.9.2
@ -155,7 +155,7 @@ require (
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // 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/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // 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 h1:7PcjxKTsfGXpTMiTNNa1VllbsYSZJN5nhvVEWQMdX8Y=
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= 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.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 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 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.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 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.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= 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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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= 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-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-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-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-20220829144015-23454907ede3 h1:4wwmycAWg7WUIFWgzxP6Wumy2GBLxmATgkhgpFnJl2U=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= 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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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{ params := common.EventParams{
Name: domain, Name: domain,
Event: "Certificate renewal",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
if err != nil { if err != nil {
params.Status = 2 params.Status = 2
params.Event = "Certificate renewal failed" params.AddError(err)
} else { } else {
params.Status = 1 params.Status = 1
params.Event = "Successful certificate renewal"
} }
common.HandleCertificateEvent(params) common.HandleCertificateEvent(params)
} }

View file

@ -122,7 +122,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
} }
var errRes error var errRes error
if hasRules { if hasRules {
errRes = eventManager.handleFsEvent(EventParams{ params := EventParams{
Name: notification.Username, Name: notification.Username,
Event: notification.Action, Event: notification.Action,
Status: notification.Status, Status: notification.Status,
@ -136,7 +136,11 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
IP: notification.IP, IP: notification.IP,
Timestamp: notification.Timestamp, Timestamp: notification.Timestamp,
Object: nil, Object: nil,
}) }
if err != nil {
params.AddError(fmt.Errorf("%q failed: %w", params.Event, err))
}
errRes = eventManager.handleFsEvent(params)
} }
if hasHook { if hasHook {
if util.Contains(Config.Actions.ExecuteSync, operation) { 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 // EventParams defines the supported event parameters
type EventParams struct { type EventParams struct {
Name string Name string
Event string Event string
Status int Status int
VirtualPath string VirtualPath string
FsPath string FsPath string
VirtualTargetPath string VirtualTargetPath string
FsTargetPath string FsTargetPath string
ObjectName string ObjectName string
ObjectType string ObjectType string
FileSize int64 FileSize int64
Protocol string Protocol string
IP string IP string
Timestamp int64 Timestamp int64
Object plugin.Renderer Object plugin.Renderer
sender string 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 // getUsers returns users with group settings not applied
@ -469,11 +498,18 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
"{{Protocol}}", p.Protocol, "{{Protocol}}", p.Protocol,
"{{IP}}", p.IP, "{{IP}}", p.IP,
"{{Timestamp}}", fmt.Sprintf("%d", p.Timestamp), "{{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 { if addObjectData {
data, err := p.Object.RenderAsJSON(p.Event != operationDelete) data, err := p.Object.RenderAsJSON(p.Event != operationDelete)
if err == nil { if err == nil {
replacements = append(replacements, "{{ObjectData}}", string(data)) replacements[len(replacements)-1] = string(data)
} }
} }
return replacements return replacements
@ -516,7 +552,7 @@ func getMailAttachments(user dataprovider.User, attachments []string, replacer *
err = user.CheckFsRoot(connectionID) err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck defer user.CloseFs() //nolint:errcheck
if err != nil { 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) conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
totalSize := int64(0) totalSize := int64(0)
@ -596,7 +632,7 @@ func getHTTPRuleActionEndpoint(c dataprovider.EventActionHTTPConfig, replacer *s
return c.Endpoint, nil 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 !c.Password.IsEmpty() {
if err := c.Password.TryDecrypt(); err != nil { if err := c.Password.TryDecrypt(); err != nil {
return fmt.Errorf("unable to decrypt password: %w", err) return fmt.Errorf("unable to decrypt password: %w", err)
@ -653,7 +689,7 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params EventPar
return nil 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)) envVars := make([]string, 0, len(c.EnvVars))
addObjectData := false addObjectData := false
if params.Object != nil { if params.Object != nil {
@ -686,7 +722,7 @@ func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params Ev
return err return err
} }
func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params EventParams) error { func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *EventParams) error {
addObjectData := false addObjectData := false
if params.Object != nil { if params.Object != nil {
if strings.Contains(c.Body, "{{ObjectData}}") { if strings.Contains(c.Body, "{{ObjectData}}") {
@ -748,7 +784,7 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
err = user.CheckFsRoot(connectionID) err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck defer user.CloseFs() //nolint:errcheck
if err != nil { 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) conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range deletes { for _, item := range deletes {
@ -775,7 +811,7 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
} }
func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer, func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams, conditions dataprovider.ConditionOptions, params *EventParams,
) error { ) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
@ -792,6 +828,7 @@ func executeDeleteFsRuleAction(deletes []string, replacer *strings.Replacer,
} }
executed++ executed++
if err = executeDeleteFsActionForUser(deletes, replacer, user); err != nil { if err = executeDeleteFsActionForUser(deletes, replacer, user); err != nil {
params.AddError(err)
failures = append(failures, user.Username) failures = append(failures, user.Username)
continue continue
} }
@ -815,7 +852,7 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
err = user.CheckFsRoot(connectionID) err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck defer user.CloseFs() //nolint:errcheck
if err != nil { 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) conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range dirs { for _, item := range dirs {
@ -832,7 +869,7 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
} }
func executeMkdirFsRuleAction(dirs []string, replacer *strings.Replacer, func executeMkdirFsRuleAction(dirs []string, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams, conditions dataprovider.ConditionOptions, params *EventParams,
) error { ) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
@ -874,7 +911,7 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
err = user.CheckFsRoot(connectionID) err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck defer user.CloseFs() //nolint:errcheck
if err != nil { 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) conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range renames { for _, item := range renames {
@ -899,7 +936,7 @@ func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
err = user.CheckFsRoot(connectionID) err = user.CheckFsRoot(connectionID)
defer user.CloseFs() //nolint:errcheck defer user.CloseFs() //nolint:errcheck
if err != nil { 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) conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
for _, item := range exist { for _, item := range exist {
@ -913,7 +950,7 @@ func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
} }
func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *strings.Replacer, func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *strings.Replacer,
conditions dataprovider.ConditionOptions, params EventParams, conditions dataprovider.ConditionOptions, params *EventParams,
) error { ) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
@ -931,6 +968,7 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
executed++ executed++
if err = executeRenameFsActionForUser(renames, replacer, user); err != nil { if err = executeRenameFsActionForUser(renames, replacer, user); err != nil {
failures = append(failures, user.Username) failures = append(failures, user.Username)
params.AddError(err)
continue continue
} }
} }
@ -945,7 +983,7 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
} }
func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions, func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions,
params EventParams, params *EventParams,
) error { ) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
@ -963,6 +1001,7 @@ func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, condit
executed++ executed++
if err = executeExistFsActionForUser(exist, replacer, user); err != nil { if err = executeExistFsActionForUser(exist, replacer, user); err != nil {
failures = append(failures, user.Username) failures = append(failures, user.Username)
params.AddError(err)
continue continue
} }
} }
@ -977,7 +1016,7 @@ func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, condit
} }
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions, func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions,
params EventParams, params *EventParams,
) error { ) error {
addObjectData := false addObjectData := false
replacements := params.getStringReplacements(addObjectData) replacements := params.getStringReplacements(addObjectData)
@ -1003,25 +1042,25 @@ func executeQuotaResetForUser(user dataprovider.User) error {
return err return err
} }
if !QuotaScans.AddUserQuotaScan(user.Username) { if !QuotaScans.AddUserQuotaScan(user.Username) {
eventManagerLog(logger.LevelError, "another quota scan is already 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 %s", user.Username) return fmt.Errorf("another quota scan is in progress for user %q", user.Username)
} }
defer QuotaScans.RemoveUserQuotaScan(user.Username) defer QuotaScans.RemoveUserQuotaScan(user.Username)
numFiles, size, err := user.ScanQuota() numFiles, size, err := user.ScanQuota()
if err != nil { if err != nil {
eventManagerLog(logger.LevelError, "error scanning quota for user %s: %v", user.Username, err) eventManagerLog(logger.LevelError, "error scanning quota for user %q: %v", user.Username, err)
return err return fmt.Errorf("error scanning quota for user %q: %w", user.Username, err)
} }
err = dataprovider.UpdateUserQuota(&user, numFiles, size, true) err = dataprovider.UpdateUserQuota(&user, numFiles, size, true)
if err != nil { if err != nil {
eventManagerLog(logger.LevelError, "error updating quota for user %s: %v", user.Username, err) eventManagerLog(logger.LevelError, "error updating quota for user %q: %v", user.Username, err)
return err return fmt.Errorf("error updating quota for user %q: %w", user.Username, err)
} }
return nil return nil
} }
func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error { func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
return fmt.Errorf("unable to get users: %w", err) return fmt.Errorf("unable to get users: %w", err)
@ -1031,12 +1070,13 @@ func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions,
for _, user := range users { for _, user := range users {
// if sender is set, the conditions have already been evaluated // if sender is set, the conditions have already been evaluated
if params.sender == "" && !checkEventConditionPatterns(user.Username, conditions.Names) { 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) user.Username)
continue continue
} }
executed++ executed++
if err = executeQuotaResetForUser(user); err != nil { if err = executeQuotaResetForUser(user); err != nil {
params.AddError(err)
failedResets = append(failedResets, user.Username) failedResets = append(failedResets, user.Username)
continue continue
} }
@ -1051,7 +1091,7 @@ func executeUsersQuotaResetRuleAction(conditions dataprovider.ConditionOptions,
return nil return nil
} }
func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error { func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
folders, err := params.getFolders() folders, err := params.getFolders()
if err != nil { if err != nil {
return fmt.Errorf("unable to get folders: %w", err) return fmt.Errorf("unable to get folders: %w", err)
@ -1066,7 +1106,8 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
continue continue
} }
if !QuotaScans.AddVFolderQuotaScan(folder.Name) { 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) failedResets = append(failedResets, folder.Name)
continue continue
} }
@ -1078,13 +1119,15 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
numFiles, size, err := f.ScanQuota() numFiles, size, err := f.ScanQuota()
QuotaScans.RemoveVFolderQuotaScan(folder.Name) QuotaScans.RemoveVFolderQuotaScan(folder.Name)
if err != nil { 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) failedResets = append(failedResets, folder.Name)
continue continue
} }
err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true) err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true)
if err != nil { 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) failedResets = append(failedResets, folder.Name)
} }
} }
@ -1098,7 +1141,7 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions
return nil return nil
} }
func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params EventParams) error { func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOptions, params *EventParams) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
return fmt.Errorf("unable to get users: %w", err) return fmt.Errorf("unable to get users: %w", err)
@ -1115,7 +1158,8 @@ func executeTransferQuotaResetRuleAction(conditions dataprovider.ConditionOption
executed++ executed++
err = dataprovider.UpdateUserTransferQuota(&user, 0, 0, true) err = dataprovider.UpdateUserTransferQuota(&user, 0, 0, true)
if err != nil { 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) failedResets = append(failedResets, user.Username)
} }
} }
@ -1140,18 +1184,18 @@ func executeDataRetentionCheckForUser(user dataprovider.User, folders []dataprov
} }
c := RetentionChecks.Add(check, &user) c := RetentionChecks.Add(check, &user)
if c == nil { if c == nil {
eventManagerLog(logger.LevelError, "another retention check is already 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 %s", user.Username) return fmt.Errorf("another retention check is in progress for user %q", user.Username)
} }
if err := c.Start(); err != nil { if err := c.Start(); err != nil {
eventManagerLog(logger.LevelError, "error checking retention for user %s: %v", user.Username, err) eventManagerLog(logger.LevelError, "error checking retention for user %q: %v", user.Username, err)
return err return fmt.Errorf("error checking retention for user %q: %w", user.Username, err)
} }
return nil return nil
} }
func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRetentionConfig, func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRetentionConfig,
conditions dataprovider.ConditionOptions, params EventParams, conditions dataprovider.ConditionOptions, params *EventParams,
) error { ) error {
users, err := params.getUsers() users, err := params.getUsers()
if err != nil { if err != nil {
@ -1169,6 +1213,7 @@ func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRete
executed++ executed++
if err = executeDataRetentionCheckForUser(user, config.Folders); err != nil { if err = executeDataRetentionCheckForUser(user, config.Folders); err != nil {
failedChecks = append(failedChecks, user.Username) failedChecks = append(failedChecks, user.Username)
params.AddError(err)
continue continue
} }
} }
@ -1182,29 +1227,37 @@ func executeDataRetentionCheckRuleAction(config dataprovider.EventActionDataRete
return nil 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 { switch action.Type {
case dataprovider.ActionTypeHTTP: case dataprovider.ActionTypeHTTP:
return executeHTTPRuleAction(action.Options.HTTPConfig, params) err = executeHTTPRuleAction(action.Options.HTTPConfig, params)
case dataprovider.ActionTypeCommand: case dataprovider.ActionTypeCommand:
return executeCommandRuleAction(action.Options.CmdConfig, params) err = executeCommandRuleAction(action.Options.CmdConfig, params)
case dataprovider.ActionTypeEmail: case dataprovider.ActionTypeEmail:
return executeEmailRuleAction(action.Options.EmailConfig, params) err = executeEmailRuleAction(action.Options.EmailConfig, params)
case dataprovider.ActionTypeBackup: case dataprovider.ActionTypeBackup:
return dataprovider.ExecuteBackup() err = dataprovider.ExecuteBackup()
case dataprovider.ActionTypeUserQuotaReset: case dataprovider.ActionTypeUserQuotaReset:
return executeUsersQuotaResetRuleAction(conditions, params) err = executeUsersQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeFolderQuotaReset: case dataprovider.ActionTypeFolderQuotaReset:
return executeFoldersQuotaResetRuleAction(conditions, params) err = executeFoldersQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeTransferQuotaReset: case dataprovider.ActionTypeTransferQuotaReset:
return executeTransferQuotaResetRuleAction(conditions, params) err = executeTransferQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeDataRetentionCheck: case dataprovider.ActionTypeDataRetentionCheck:
return executeDataRetentionCheckRuleAction(action.Options.RetentionConfig, conditions, params) err = executeDataRetentionCheckRuleAction(action.Options.RetentionConfig, conditions, params)
case dataprovider.ActionTypeFilesystem: case dataprovider.ActionTypeFilesystem:
return executeFsRuleAction(action.Options.FsConfig, conditions, params) err = executeFsRuleAction(action.Options.FsConfig, conditions, params)
default: 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 { func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams) error {
@ -1212,10 +1265,11 @@ func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams)
for _, rule := range rules { for _, rule := range rules {
var failedActions []string var failedActions []string
paramsCopy := params.getACopy()
for _, action := range rule.Actions { for _, action := range rule.Actions {
if !action.Options.IsFailureAction && action.Options.ExecuteSync { if !action.Options.IsFailureAction && action.Options.ExecuteSync {
startTime := time.Now() 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", eventManagerLog(logger.LevelError, "unable to execute sync action %q for rule %q, elapsed %s, err: %v",
action.Name, rule.Name, time.Since(startTime), err) action.Name, rule.Name, time.Since(startTime), err)
failedActions = append(failedActions, action.Name) failedActions = append(failedActions, action.Name)
@ -1231,7 +1285,7 @@ func executeSyncRulesActions(rules []dataprovider.EventRule, params EventParams)
} }
} }
// execute async actions if any, including failure actions // execute async actions if any, including failure actions
go executeRuleAsyncActions(rule, params, failedActions) go executeRuleAsyncActions(rule, paramsCopy, failedActions)
} }
return errRes return errRes
@ -1242,11 +1296,11 @@ func executeAsyncRulesActions(rules []dataprovider.EventRule, params EventParams
defer eventManager.removeAsyncTask() defer eventManager.removeAsyncTask()
for _, rule := range rules { 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 { for _, action := range rule.Actions {
if !action.Options.IsFailureAction && !action.Options.ExecuteSync { if !action.Options.IsFailureAction && !action.Options.ExecuteSync {
startTime := time.Now() startTime := time.Now()
@ -1361,9 +1415,9 @@ func (j *eventCronJob) Run() {
} }
}(task.Name) }(task.Name)
executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{}) executeAsyncRulesActions([]dataprovider.EventRule{rule}, EventParams{Status: 1, updateStatusFromError: true})
} else { } 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) 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() _, err = params.getFolders()
assert.Error(t, err) assert.Error(t, err)
err = executeUsersQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{}) err = executeUsersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeFoldersQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{}) err = executeFoldersQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{}) err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{}) err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{}) err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{}) err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
err = executeExistFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{}) err = executeExistFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, &EventParams{})
assert.Error(t, err) assert.Error(t, err)
groupName := "agroup" 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{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "username1", Pattern: "username1",
@ -421,10 +421,10 @@ func TestEventRuleActions(t *testing.T) {
Name: actionName, Name: actionName,
Type: dataprovider.ActionTypeBackup, Type: dataprovider.ActionTypeBackup,
} }
err := executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{}) err := executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
assert.NoError(t, err) assert.NoError(t, err)
action.Type = -1 action.Type = -1
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{}) err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
assert.Error(t, err) assert.Error(t, err)
action = dataprovider.BaseEventAction{ action = dataprovider.BaseEventAction{
@ -454,12 +454,12 @@ func TestEventRuleActions(t *testing.T) {
}, },
} }
action.Options.SetEmptySecretsIfNil() action.Options.SetEmptySecretsIfNil()
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{}) err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{})
if assert.Error(t, err) { if assert.Error(t, err) {
assert.Contains(t, err.Error(), "invalid endpoint") assert.Contains(t, err.Error(), "invalid endpoint")
} }
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v", httpAddr) action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v", httpAddr)
params := EventParams{ params := &EventParams{
Name: "a", Name: "a",
Object: &dataprovider.User{ Object: &dataprovider.User{
BaseUser: sdk.BaseUser{ BaseUser: sdk.BaseUser{
@ -472,7 +472,7 @@ func TestEventRuleActions(t *testing.T) {
action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr) action.Options.HTTPConfig.Endpoint = fmt.Sprintf("http://%v/404", httpAddr)
err = executeRuleAction(action, params, dataprovider.ConditionOptions{}) err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
if assert.Error(t, err) { 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" action.Options.HTTPConfig.Endpoint = "http://invalid:1234"
err = executeRuleAction(action, params, dataprovider.ConditionOptions{}) err = executeRuleAction(action, params, dataprovider.ConditionOptions{})
@ -517,7 +517,7 @@ func TestEventRuleActions(t *testing.T) {
action = dataprovider.BaseEventAction{ action = dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeUserQuotaReset, Type: dataprovider.ActionTypeUserQuotaReset,
} }
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -530,7 +530,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = os.WriteFile(filepath.Join(user1.GetHomeDir(), "file.txt"), []byte("user"), 0666) err = os.WriteFile(filepath.Join(user1.GetHomeDir(), "file.txt"), []byte("user"), 0666)
assert.NoError(t, err) assert.NoError(t, err)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -544,7 +544,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(4), userGet.UsedQuotaSize) assert.Equal(t, int64(4), userGet.UsedQuotaSize)
// simulate another quota scan in progress // simulate another quota scan in progress
assert.True(t, QuotaScans.AddUserQuotaScan(username1)) assert.True(t, QuotaScans.AddUserQuotaScan(username1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -554,7 +554,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
assert.True(t, QuotaScans.RemoveUserQuotaScan(username1)) assert.True(t, QuotaScans.RemoveUserQuotaScan(username1))
// non matching pattern // non matching pattern
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "don't match", 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{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -622,7 +622,7 @@ func TestEventRuleActions(t *testing.T) {
err = os.Chtimes(file4, timeBeforeRetention, timeBeforeRetention) err = os.Chtimes(file4, timeBeforeRetention, timeBeforeRetention)
assert.NoError(t, err) assert.NoError(t, err)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -637,7 +637,7 @@ func TestEventRuleActions(t *testing.T) {
// simulate another check in progress // simulate another check in progress
c := RetentionChecks.Add(RetentionCheck{}, &user1) c := RetentionChecks.Add(RetentionCheck{}, &user1)
assert.NotNil(t, c) assert.NotNil(t, c)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -647,7 +647,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
RetentionChecks.remove(user1.Username) RetentionChecks.remove(user1.Username)
err = executeRuleAction(dataRetentionAction, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(dataRetentionAction, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", 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{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", Pattern: "no match",
@ -677,7 +677,7 @@ func TestEventRuleActions(t *testing.T) {
if assert.Error(t, err) { if assert.Error(t, err) {
assert.Contains(t, err.Error(), "no existence check executed") assert.Contains(t, err.Error(), "no existence check executed")
} }
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -686,7 +686,7 @@ func TestEventRuleActions(t *testing.T) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
action.Options.FsConfig.Exist = []string{"/file1.txt", path.Join("/", retentionDir, "file2.txt")} 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{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -702,7 +702,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
action.Type = dataprovider.ActionTypeTransferQuotaReset action.Type = dataprovider.ActionTypeTransferQuotaReset
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username1, Pattern: username1,
@ -715,7 +715,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer) assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer)
assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer) assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", 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{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", Pattern: "no match",
@ -753,7 +753,7 @@ func TestEventRuleActions(t *testing.T) {
Deletes: []string{"/dir1"}, Deletes: []string{"/dir1"},
}, },
} }
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", Pattern: "no match",
@ -769,7 +769,7 @@ func TestEventRuleActions(t *testing.T) {
Deletes: []string{"/dir1"}, Deletes: []string{"/dir1"},
}, },
} }
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no match", Pattern: "no match",
@ -802,7 +802,7 @@ func TestEventRuleActions(t *testing.T) {
action = dataprovider.BaseEventAction{ action = dataprovider.BaseEventAction{
Type: dataprovider.ActionTypeFolderQuotaReset, Type: dataprovider.ActionTypeFolderQuotaReset,
} }
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: foldername1, Pattern: foldername1,
@ -814,7 +814,7 @@ func TestEventRuleActions(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = os.WriteFile(filepath.Join(folder1.MappedPath, "file.txt"), []byte("folder"), 0666) err = os.WriteFile(filepath.Join(folder1.MappedPath, "file.txt"), []byte("folder"), 0666)
assert.NoError(t, err) assert.NoError(t, err)
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: foldername1, Pattern: foldername1,
@ -828,7 +828,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Equal(t, int64(6), folderGet.UsedQuotaSize) assert.Equal(t, int64(6), folderGet.UsedQuotaSize)
// simulate another quota scan in progress // simulate another quota scan in progress
assert.True(t, QuotaScans.AddVFolderQuotaScan(foldername1)) assert.True(t, QuotaScans.AddVFolderQuotaScan(foldername1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: foldername1, Pattern: foldername1,
@ -838,7 +838,7 @@ func TestEventRuleActions(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
assert.True(t, QuotaScans.RemoveVFolderQuotaScan(foldername1)) assert.True(t, QuotaScans.RemoveVFolderQuotaScan(foldername1))
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{ err = executeRuleAction(action, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: "no folder match", Pattern: "no folder match",
@ -920,7 +920,7 @@ func TestGetFileContent(t *testing.T) {
} }
func TestFilesystemActionErrors(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) { if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported filesystem action") assert.Contains(t, err.Error(), "unsupported filesystem action")
} }
@ -950,7 +950,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Subject: "subject", Subject: "subject",
Body: "body", Body: "body",
Attachments: []string{"/file.txt"}, Attachments: []string{"/file.txt"},
}, EventParams{ }, &EventParams{
sender: username, sender: username,
}) })
assert.Error(t, err) assert.Error(t, err)
@ -973,7 +973,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Subject: "subject", Subject: "subject",
Body: "body", Body: "body",
Attachments: []string{"/file1.txt"}, Attachments: []string{"/file1.txt"},
}, EventParams{ }, &EventParams{
sender: username, sender: username,
}) })
assert.Error(t, err) assert.Error(t, err)
@ -1008,7 +1008,7 @@ func TestFilesystemActionErrors(t *testing.T) {
}, },
}, },
}, },
}, EventParams{}, dataprovider.ConditionOptions{ }, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username, Pattern: username,
@ -1045,7 +1045,7 @@ func TestFilesystemActionErrors(t *testing.T) {
Deletes: []string{"/adir/sub/f.dat"}, Deletes: []string{"/adir/sub/f.dat"},
}, },
}, },
}, EventParams{}, dataprovider.ConditionOptions{ }, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username, Pattern: username,
@ -1071,7 +1071,7 @@ func TestFilesystemActionErrors(t *testing.T) {
MkDirs: []string{"/adir/sub/sub1"}, MkDirs: []string{"/adir/sub/sub1"},
}, },
}, },
}, EventParams{}, dataprovider.ConditionOptions{ }, &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username, Pattern: username,
@ -1119,7 +1119,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm) err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err) assert.NoError(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeUserQuotaReset}, err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeUserQuotaReset},
EventParams{}, dataprovider.ConditionOptions{ &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username, Pattern: username,
@ -1129,7 +1129,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeTransferQuotaReset}, err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeTransferQuotaReset},
EventParams{}, dataprovider.ConditionOptions{ &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: username, Pattern: username,
@ -1154,7 +1154,7 @@ func TestQuotaActionsWithQuotaTrackDisabled(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeFolderQuotaReset}, err = executeRuleAction(dataprovider.BaseEventAction{Type: dataprovider.ActionTypeFolderQuotaReset},
EventParams{}, dataprovider.ConditionOptions{ &EventParams{}, dataprovider.ConditionOptions{
Names: []dataprovider.ConditionPattern{ Names: []dataprovider.ConditionPattern{
{ {
Pattern: foldername, Pattern: foldername,
@ -1242,3 +1242,37 @@ func TestScheduledActions(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
stopEventScheduler() 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" "bytes"
"crypto/rand" "crypto/rand"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"math" "math"
@ -3064,8 +3065,8 @@ func TestEventRule(t *testing.T) {
Options: dataprovider.BaseEventActionOptions{ Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{ EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"test1@example.com", "test2@example.com"}, Recipients: []string{"test1@example.com", "test2@example.com"},
Subject: `New "{{Event}}" from "{{Name}}"`, Subject: `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}}", 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{ EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"failure@example.com"}, Recipients: []string{"failure@example.com"},
Subject: `Failed "{{Event}}" from "{{Name}}"`, 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") uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
u := getTestUser() u := getTestUser()
u.DownloadDataTransfer = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated) user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
movedFileName := "moved.dat" movedFileName := "moved.dat"
@ -3230,6 +3232,8 @@ func TestEventRule(t *testing.T) {
dirName := "subdir" dirName := "subdir"
err = client.Mkdir(dirName) err = client.Mkdir(dirName)
assert.NoError(t, err) assert.NoError(t, err)
err = client.Mkdir("subdir1")
assert.NoError(t, err)
// rule conditions match // rule conditions match
lastReceivedEmail.reset() lastReceivedEmail.reset()
err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client) err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
@ -3247,7 +3251,32 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2) assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com")) assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@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 // remove the upload script to test the failure action
err = os.Remove(uploadScriptPath) err = os.Remove(uploadScriptPath)
assert.NoError(t, err) assert.NoError(t, err)
@ -3260,10 +3289,11 @@ func TestEventRule(t *testing.T) {
email = lastReceivedEmail.get() email = lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "failure@example.com")) 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 // now test the download rule
lastReceivedEmail.reset() lastReceivedEmail.reset()
f, err := client.Open(movedFileName) f, err = client.Open(movedFileName)
assert.NoError(t, err) assert.NoError(t, err)
contents, err := io.ReadAll(f) contents, err := io.ReadAll(f)
assert.NoError(t, err) assert.NoError(t, err)
@ -3277,7 +3307,7 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2) assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com")) assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@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) _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@ -3291,7 +3321,7 @@ func TestEventRule(t *testing.T) {
assert.Len(t, email.To, 2) assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test1@example.com")) assert.True(t, util.Contains(email.To, "test1@example.com"))
assert.True(t, util.Contains(email.To, "test2@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) _, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK) _, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
@ -3443,7 +3473,7 @@ func TestEventRuleProviderEvents(t *testing.T) {
email := lastReceivedEmail.get() email := lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test3@example.com")) 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 // now delete the script to generate an error
lastReceivedEmail.reset() lastReceivedEmail.reset()
@ -3458,8 +3488,8 @@ func TestEventRuleProviderEvents(t *testing.T) {
email := lastReceivedEmail.get() email := lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "failure@example.com")) assert.True(t, util.Contains(email.To, "failure@example.com"))
assert.Contains(t, string(email.Data), `Subject: Failed "update" from "admin"`) assert.Contains(t, 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, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
lastReceivedEmail.reset() lastReceivedEmail.reset()
// generate an error for the failure action // generate an error for the failure action
smtpCfg = smtp.Config{} smtpCfg = smtp.Config{}
@ -3830,8 +3860,8 @@ func TestEventActionEmailAttachments(t *testing.T) {
email := lastReceivedEmail.get() email := lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com")) 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, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
assert.Contains(t, string(email.Data), "Content-Disposition: attachment") assert.Contains(t, email.Data, "Content-Disposition: attachment")
} }
} }
@ -3929,7 +3959,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
email := lastReceivedEmail.get() email := lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com")) 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() lastReceivedEmail.reset()
// a new upload will not produce a new notification // a new upload will not produce a new notification
err = writeSFTPFileNoCheck(testFileName+"_1", 32768, client) err = writeSFTPFileNoCheck(testFileName+"_1", 32768, client)
@ -3952,7 +3982,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
email = lastReceivedEmail.get() email = lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com")) 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 // download again
lastReceivedEmail.reset() lastReceivedEmail.reset()
f, err = client.Open(testFileName) f, err = client.Open(testFileName)
@ -4001,8 +4031,8 @@ func TestEventRuleCertificate(t *testing.T) {
Options: dataprovider.BaseEventActionOptions{ Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{ EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"test@example.com"}, Recipients: []string{"test@example.com"},
Subject: `"{{Event}}"`, Subject: `"{{Event}} {{StatusString}}"`,
Body: "Domain: {{Name}} Timestamp: {{Timestamp}}", Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
}, },
}, },
} }
@ -4051,11 +4081,13 @@ func TestEventRuleCertificate(t *testing.T) {
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated) rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
renewalEvent := "Certificate renewal"
common.HandleCertificateEvent(common.EventParams{ common.HandleCertificateEvent(common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Status: 1, Status: 1,
Event: "Successful certificate renewal", Event: renewalEvent,
}) })
assert.Eventually(t, func() bool { assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != "" return lastReceivedEmail.get().From != ""
@ -4063,24 +4095,28 @@ func TestEventRuleCertificate(t *testing.T) {
email := lastReceivedEmail.get() email := lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com")) assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), `Subject: "Successful certificate renewal"`) assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`) assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
lastReceivedEmail.reset() lastReceivedEmail.reset()
common.HandleCertificateEvent(common.EventParams{ params := common.EventParams{
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Status: 2, 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 { assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != "" return lastReceivedEmail.get().From != ""
}, 3000*time.Millisecond, 100*time.Millisecond) }, 3000*time.Millisecond, 100*time.Millisecond)
email = lastReceivedEmail.get() email = lastReceivedEmail.get()
assert.Len(t, email.To, 1) assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com")) assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, string(email.Data), `Subject: "Certificate renewal failed"`) assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
assert.Contains(t, string(email.Data), `Domain: example.com Timestamp`) assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
assert.Contains(t, email.Data, errRenew.Error())
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK) _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
@ -4095,7 +4131,7 @@ func TestEventRuleCertificate(t *testing.T) {
Name: "example.com", Name: "example.com",
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Status: 1, Status: 1,
Event: "Successful certificate renewal", Event: renewalEvent,
}) })
smtpCfg = smtp.Config{} smtpCfg = smtp.Config{}
@ -4184,7 +4220,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
lastReceivedEmail.reset() lastReceivedEmail.reset()
time.Sleep(300 * time.Millisecond) 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++ { for i := 0; i < 3; i++ {
user.Password = "wrong_pwd" user.Password = "wrong_pwd"
@ -4203,7 +4239,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
assert.Len(t, email.To, 2) assert.Len(t, email.To, 2)
assert.True(t, util.Contains(email.To, "test3@example.com")) assert.True(t, util.Contains(email.To, "test3@example.com"))
assert.True(t, util.Contains(email.To, "test4@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, "", "") err = dataprovider.DeleteEventRule(rule1.Name, "", "")
assert.NoError(t, err) assert.NoError(t, err)
@ -5305,7 +5341,7 @@ type receivedEmail struct {
sync.RWMutex sync.RWMutex
From string From string
To []string To []string
Data []byte Data string
} }
func (e *receivedEmail) set(from string, to []string, data []byte) { 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.From = from
e.To = to e.To = to
e.Data = data e.Data = strings.ReplaceAll(string(data), "=\r\n", "")
} }
func (e *receivedEmail) reset() { func (e *receivedEmail) reset() {
@ -5323,7 +5359,7 @@ func (e *receivedEmail) reset() {
e.From = "" e.From = ""
e.To = nil e.To = nil
e.Data = nil e.Data = ""
} }
func (e *receivedEmail) get() receivedEmail { func (e *receivedEmail) get() receivedEmail {

View file

@ -541,6 +541,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<p> <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. <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>
<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> <p>
<span class="shortcut"><b>{{`{{VirtualPath}}`}}</b></span> => Path seen by SFTPGo users, for example "/adir/afile.txt". <span class="shortcut"><b>{{`{{VirtualPath}}`}}</b></span> => Path seen by SFTPGo users, for example "/adir/afile.txt".
</p> </p>