eventmanager placeholders: add StatusString and ErrorString
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
37d98ca290
commit
56bf51277c
9 changed files with 285 additions and 150 deletions
|
@ -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
4
go.mod
|
@ -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
9
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -421,6 +421,35 @@ type EventParams struct {
|
|||
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 ¶ms
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue