mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-28 18:40:35 +00:00
eventmanager: add path exists filesystem action
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
57935f585c
commit
9ddd2d3588
12 changed files with 327 additions and 42 deletions
|
@ -16,6 +16,7 @@ The following actions are supported:
|
||||||
- `Rename`. You can rename one or more files or directories.
|
- `Rename`. You can rename one or more files or directories.
|
||||||
- `Delete`. You can delete one or more files and directories.
|
- `Delete`. You can delete one or more files and directories.
|
||||||
- `Create directories`. You can create one or more directories including sub-directories.
|
- `Create directories`. You can create one or more directories including sub-directories.
|
||||||
|
- `Path exists`. Check if the specified path exists.
|
||||||
|
|
||||||
The following placeholders are supported:
|
The following placeholders are supported:
|
||||||
|
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -65,8 +65,8 @@ require (
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.1
|
||||||
gocloud.dev v0.26.0
|
gocloud.dev v0.26.0
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
|
||||||
golang.org/x/net v0.0.0-20220811182439-13a9a731de15
|
golang.org/x/net v0.0.0-20220812174116-3211cb980234
|
||||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
|
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
|
||||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6
|
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
||||||
|
@ -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-20220817144833-d7fd3f11b9b1 // indirect
|
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa // indirect
|
||||||
google.golang.org/grpc v1.48.0 // indirect
|
google.golang.org/grpc v1.48.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
|
||||||
|
@ -167,6 +167,6 @@ require (
|
||||||
replace (
|
replace (
|
||||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||||
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d
|
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d
|
||||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e
|
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82
|
||||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee
|
golang.org/x/net => github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738
|
||||||
)
|
)
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -262,12 +262,12 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ
|
||||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||||
github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e h1:ZvOJ5DqEUZig5lGlwGy78KrSIk9OpXFOpoBFrZC0HCo=
|
github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82 h1:TezoLY9GhuhvionRxoU1FyfIbbC2lOm+OipXzuXAC2A=
|
||||||
github.com/drakkan/crypto v0.0.0-20220723143649-81550382d55e/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro=
|
github.com/drakkan/crypto v0.0.0-20220820120743-96e237d06b82/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro=
|
||||||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
|
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
|
||||||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||||
github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee h1:hTHRVJ//MvApWBRVLrZOCfh+row8txg1G9BJVKsq+qk=
|
github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738 h1:y++pz0G+bwPzCJyHiqslCAXzAWj4Azk5FbGTwZ5nq0g=
|
||||||
github.com/drakkan/net v0.0.0-20220812153436-025c6c7680ee/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
github.com/drakkan/net v0.0.0-20220820120527-aa746bf1d738/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM=
|
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM=
|
||||||
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
|
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
|
||||||
|
@ -1229,8 +1229,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
|
||||||
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/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-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||||
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 h1:C2UVWqrgLYKrT5nh5oU6hLRm1AeEklCK5eloQA1NtFY=
|
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa h1:Ux9yJCyf598uEniFPSyp8g1jtGTt77m+lzYyVgrWQaQ=
|
||||||
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa/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=
|
||||||
|
|
|
@ -662,7 +662,7 @@ func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer,
|
||||||
}
|
}
|
||||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||||
for _, item := range deletes {
|
for _, item := range deletes {
|
||||||
item = replaceWithReplacer(item, replacer)
|
item = util.CleanPath(replaceWithReplacer(item, replacer))
|
||||||
info, err := conn.DoStat(item, 0, false)
|
info, err := conn.DoStat(item, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if conn.IsNotExistError(err) {
|
if conn.IsNotExistError(err) {
|
||||||
|
@ -729,7 +729,7 @@ func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, use
|
||||||
}
|
}
|
||||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||||
for _, item := range dirs {
|
for _, item := range dirs {
|
||||||
item = replaceWithReplacer(item, replacer)
|
item = util.CleanPath(replaceWithReplacer(item, replacer))
|
||||||
if err = conn.CheckParentDirs(path.Dir(item)); err != nil {
|
if err = conn.CheckParentDirs(path.Dir(item)); err != nil {
|
||||||
return fmt.Errorf("unable to check parent dirs for %q, user %q: %w", item, user.Username, err)
|
return fmt.Errorf("unable to check parent dirs for %q, user %q: %w", item, user.Username, err)
|
||||||
}
|
}
|
||||||
|
@ -788,8 +788,8 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
|
||||||
}
|
}
|
||||||
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||||
for _, item := range renames {
|
for _, item := range renames {
|
||||||
source := replaceWithReplacer(item.Key, replacer)
|
source := util.CleanPath(replaceWithReplacer(item.Key, replacer))
|
||||||
target := replaceWithReplacer(item.Value, replacer)
|
target := util.CleanPath(replaceWithReplacer(item.Value, replacer))
|
||||||
if err = conn.Rename(source, target); err != nil {
|
if err = conn.Rename(source, target); err != nil {
|
||||||
return fmt.Errorf("unable to rename %q->%q, user %q: %w", source, target, user.Username, err)
|
return fmt.Errorf("unable to rename %q->%q, user %q: %w", source, target, user.Username, err)
|
||||||
}
|
}
|
||||||
|
@ -798,6 +798,30 @@ func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeExistFsActionForUser(exist []string, replacer *strings.Replacer,
|
||||||
|
user dataprovider.User,
|
||||||
|
) error {
|
||||||
|
user, err := getUserForEventAction(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connectionID := fmt.Sprintf("%s_%s", protocolEventAction, xid.New().String())
|
||||||
|
err = user.CheckFsRoot(connectionID)
|
||||||
|
defer user.CloseFs() //nolint:errcheck
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn := NewBaseConnection(connectionID, protocolEventAction, "", "", user)
|
||||||
|
for _, item := range exist {
|
||||||
|
item = util.CleanPath(replaceWithReplacer(item, replacer))
|
||||||
|
if _, err = conn.DoStat(item, 0, false); err != nil {
|
||||||
|
return fmt.Errorf("error checking existence for path %q, user %q: %w", item, user.Username, err)
|
||||||
|
}
|
||||||
|
eventManagerLog(logger.LevelDebug, "path %q exists for user %q", item, user.Username)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -830,6 +854,38 @@ func executeRenameFsRuleAction(renames []dataprovider.KeyValue, replacer *string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions,
|
||||||
|
params EventParams,
|
||||||
|
) error {
|
||||||
|
users, err := params.getUsers()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get users: %w", err)
|
||||||
|
}
|
||||||
|
var failures []string
|
||||||
|
executed := 0
|
||||||
|
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 fs exist for user %s, name conditions don't match",
|
||||||
|
user.Username)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
executed++
|
||||||
|
if err = executeExistFsActionForUser(exist, replacer, user); err != nil {
|
||||||
|
failures = append(failures, user.Username)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return fmt.Errorf("fs existence check failed for users: %+v", failures)
|
||||||
|
}
|
||||||
|
if executed == 0 {
|
||||||
|
eventManagerLog(logger.LevelError, "no existence check executed")
|
||||||
|
return errors.New("no existence check executed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions,
|
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions,
|
||||||
params EventParams,
|
params EventParams,
|
||||||
) error {
|
) error {
|
||||||
|
@ -843,6 +899,8 @@ func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions
|
||||||
return executeDeleteFsRuleAction(c.Deletes, replacer, conditions, params)
|
return executeDeleteFsRuleAction(c.Deletes, replacer, conditions, params)
|
||||||
case dataprovider.FilesystemActionMkdirs:
|
case dataprovider.FilesystemActionMkdirs:
|
||||||
return executeMkdirFsRuleAction(c.MkDirs, replacer, conditions, params)
|
return executeMkdirFsRuleAction(c.MkDirs, replacer, conditions, params)
|
||||||
|
case dataprovider.FilesystemActionExist:
|
||||||
|
return executeExistFsRuleAction(c.Exist, replacer, conditions, params)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported filesystem action %d", c.Type)
|
return fmt.Errorf("unsupported filesystem action %d", c.Type)
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,6 +281,8 @@ func TestEventManagerErrors(t *testing.T) {
|
||||||
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{})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
groupName := "agroup"
|
groupName := "agroup"
|
||||||
err = executeQuotaResetForUser(dataprovider.User{
|
err = executeQuotaResetForUser(dataprovider.User{
|
||||||
|
@ -328,6 +330,15 @@ func TestEventManagerErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
err = executeExistFsActionForUser(nil, nil, dataprovider.User{
|
||||||
|
Groups: []sdk.GroupMapping{
|
||||||
|
{
|
||||||
|
Name: groupName,
|
||||||
|
Type: sdk.GroupTypePrimary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
dataRetentionAction := dataprovider.BaseEventAction{
|
dataRetentionAction := dataprovider.BaseEventAction{
|
||||||
Type: dataprovider.ActionTypeDataRetentionCheck,
|
Type: dataprovider.ActionTypeDataRetentionCheck,
|
||||||
|
@ -637,6 +648,43 @@ func TestEventRuleActions(t *testing.T) {
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Contains(t, err.Error(), "no retention check executed")
|
assert.Contains(t, err.Error(), "no retention check executed")
|
||||||
}
|
}
|
||||||
|
// test file exists action
|
||||||
|
action = dataprovider.BaseEventAction{
|
||||||
|
Type: dataprovider.ActionTypeFilesystem,
|
||||||
|
Options: dataprovider.BaseEventActionOptions{
|
||||||
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||||
|
Type: dataprovider.FilesystemActionExist,
|
||||||
|
Exist: []string{"/file1.txt", path.Join("/", retentionDir, "file3.txt")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||||
|
Names: []dataprovider.ConditionPattern{
|
||||||
|
{
|
||||||
|
Pattern: "no match",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Contains(t, err.Error(), "no existence check executed")
|
||||||
|
}
|
||||||
|
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||||
|
Names: []dataprovider.ConditionPattern{
|
||||||
|
{
|
||||||
|
Pattern: username1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
action.Options.FsConfig.Exist = []string{"/file1.txt", path.Join("/", retentionDir, "file2.txt")}
|
||||||
|
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||||
|
Names: []dataprovider.ConditionPattern{
|
||||||
|
{
|
||||||
|
Pattern: username1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
err = os.RemoveAll(user1.GetHomeDir())
|
err = os.RemoveAll(user1.GetHomeDir())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -838,6 +886,8 @@ func TestFilesystemActionErrors(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
err = executeRenameFsActionForUser(nil, testReplacer, user)
|
err = executeRenameFsActionForUser(nil, testReplacer, user)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
err = executeExistFsActionForUser(nil, testReplacer, user)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
user.FsConfig.Provider = sdk.LocalFilesystemProvider
|
user.FsConfig.Provider = sdk.LocalFilesystemProvider
|
||||||
user.Permissions["/"] = []string{dataprovider.PermUpload}
|
user.Permissions["/"] = []string{dataprovider.PermUpload}
|
||||||
|
|
|
@ -3535,6 +3535,16 @@ func TestEventRuleFsActions(t *testing.T) {
|
||||||
Name: "a5",
|
Name: "a5",
|
||||||
Type: dataprovider.ActionTypeUserQuotaReset,
|
Type: dataprovider.ActionTypeUserQuotaReset,
|
||||||
}
|
}
|
||||||
|
a6 := dataprovider.BaseEventAction{
|
||||||
|
Name: "a6",
|
||||||
|
Type: dataprovider.ActionTypeFilesystem,
|
||||||
|
Options: dataprovider.BaseEventActionOptions{
|
||||||
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||||
|
Type: dataprovider.FilesystemActionExist,
|
||||||
|
Exist: []string{"/{{VirtualPath}}"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||||
assert.NoError(t, err, string(resp))
|
assert.NoError(t, err, string(resp))
|
||||||
action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
||||||
|
@ -3545,6 +3555,8 @@ func TestEventRuleFsActions(t *testing.T) {
|
||||||
assert.NoError(t, err, string(resp))
|
assert.NoError(t, err, string(resp))
|
||||||
action5, resp, err := httpdtest.AddEventAction(a5, http.StatusCreated)
|
action5, resp, err := httpdtest.AddEventAction(a5, http.StatusCreated)
|
||||||
assert.NoError(t, err, string(resp))
|
assert.NoError(t, err, string(resp))
|
||||||
|
action6, resp, err := httpdtest.AddEventAction(a6, http.StatusCreated)
|
||||||
|
assert.NoError(t, err, string(resp))
|
||||||
|
|
||||||
r1 := dataprovider.EventRule{
|
r1 := dataprovider.EventRule{
|
||||||
Name: "r1",
|
Name: "r1",
|
||||||
|
@ -3598,6 +3610,12 @@ func TestEventRuleFsActions(t *testing.T) {
|
||||||
},
|
},
|
||||||
Order: 1,
|
Order: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
BaseEventAction: dataprovider.BaseEventAction{
|
||||||
|
Name: action6.Name,
|
||||||
|
},
|
||||||
|
Order: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r4 := dataprovider.EventRule{
|
r4 := dataprovider.EventRule{
|
||||||
|
@ -3742,6 +3760,8 @@ func TestEventRuleFsActions(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = httpdtest.RemoveEventAction(action5, http.StatusOK)
|
_, err = httpdtest.RemoveEventAction(action5, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
_, err = httpdtest.RemoveEventAction(action6, http.StatusOK)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEventRuleCertificate(t *testing.T) {
|
func TestEventRuleCertificate(t *testing.T) {
|
||||||
|
|
|
@ -117,10 +117,12 @@ const (
|
||||||
FilesystemActionRename = iota + 1
|
FilesystemActionRename = iota + 1
|
||||||
FilesystemActionDelete
|
FilesystemActionDelete
|
||||||
FilesystemActionMkdirs
|
FilesystemActionMkdirs
|
||||||
|
FilesystemActionExist
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
supportedFsActions = []int{FilesystemActionRename, FilesystemActionDelete, FilesystemActionMkdirs}
|
supportedFsActions = []int{FilesystemActionRename, FilesystemActionDelete, FilesystemActionMkdirs,
|
||||||
|
FilesystemActionExist}
|
||||||
)
|
)
|
||||||
|
|
||||||
func isFilesystemActionValid(value int) bool {
|
func isFilesystemActionValid(value int) bool {
|
||||||
|
@ -133,6 +135,8 @@ func getFsActionTypeAsString(value int) string {
|
||||||
return "Rename"
|
return "Rename"
|
||||||
case FilesystemActionDelete:
|
case FilesystemActionDelete:
|
||||||
return "Delete"
|
return "Delete"
|
||||||
|
case FilesystemActionExist:
|
||||||
|
return "Paths exist"
|
||||||
default:
|
default:
|
||||||
return "Create directories"
|
return "Create directories"
|
||||||
}
|
}
|
||||||
|
@ -384,6 +388,8 @@ type EventActionFilesystemConfig struct {
|
||||||
MkDirs []string `json:"mkdirs,omitempty"`
|
MkDirs []string `json:"mkdirs,omitempty"`
|
||||||
// files/dirs to delete
|
// files/dirs to delete
|
||||||
Deletes []string `json:"deletes,omitempty"`
|
Deletes []string `json:"deletes,omitempty"`
|
||||||
|
// file/dirs to check for existence
|
||||||
|
Exist []string `json:"exist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDeletesAsString returns the list of items to delete as comma separated string.
|
// GetDeletesAsString returns the list of items to delete as comma separated string.
|
||||||
|
@ -398,15 +404,21 @@ func (c EventActionFilesystemConfig) GetMkDirsAsString() string {
|
||||||
return strings.Join(c.MkDirs, ",")
|
return strings.Join(c.MkDirs, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExistAsString returns the list of items to check for existence as comma separated string.
|
||||||
|
// Using a pointer receiver will not work in web templates
|
||||||
|
func (c EventActionFilesystemConfig) GetExistAsString() string {
|
||||||
|
return strings.Join(c.Exist, ",")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *EventActionFilesystemConfig) validateRenames() error {
|
func (c *EventActionFilesystemConfig) validateRenames() error {
|
||||||
if len(c.Renames) == 0 {
|
if len(c.Renames) == 0 {
|
||||||
return util.NewValidationError("no items to rename specified")
|
return util.NewValidationError("no path to rename specified")
|
||||||
}
|
}
|
||||||
for idx, kv := range c.Renames {
|
for idx, kv := range c.Renames {
|
||||||
key := strings.TrimSpace(kv.Key)
|
key := strings.TrimSpace(kv.Key)
|
||||||
value := strings.TrimSpace(kv.Value)
|
value := strings.TrimSpace(kv.Value)
|
||||||
if key == "" || value == "" {
|
if key == "" || value == "" {
|
||||||
return util.NewValidationError("invalid items to rename")
|
return util.NewValidationError("invalid paths to rename")
|
||||||
}
|
}
|
||||||
key = util.CleanPath(key)
|
key = util.CleanPath(key)
|
||||||
value = util.CleanPath(value)
|
value = util.CleanPath(value)
|
||||||
|
@ -424,6 +436,51 @@ func (c *EventActionFilesystemConfig) validateRenames() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *EventActionFilesystemConfig) validateDeletes() error {
|
||||||
|
if len(c.Deletes) == 0 {
|
||||||
|
return util.NewValidationError("no path to delete specified")
|
||||||
|
}
|
||||||
|
for idx, val := range c.Deletes {
|
||||||
|
val = strings.TrimSpace(val)
|
||||||
|
if val == "" {
|
||||||
|
return util.NewValidationError("invalid path to delete")
|
||||||
|
}
|
||||||
|
c.Deletes[idx] = util.CleanPath(val)
|
||||||
|
}
|
||||||
|
c.Deletes = util.RemoveDuplicates(c.Deletes, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EventActionFilesystemConfig) validateMkdirs() error {
|
||||||
|
if len(c.MkDirs) == 0 {
|
||||||
|
return util.NewValidationError("no directory to create specified")
|
||||||
|
}
|
||||||
|
for idx, val := range c.MkDirs {
|
||||||
|
val = strings.TrimSpace(val)
|
||||||
|
if val == "" {
|
||||||
|
return util.NewValidationError("invalid directory to create")
|
||||||
|
}
|
||||||
|
c.MkDirs[idx] = util.CleanPath(val)
|
||||||
|
}
|
||||||
|
c.MkDirs = util.RemoveDuplicates(c.MkDirs, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *EventActionFilesystemConfig) validateExist() error {
|
||||||
|
if len(c.Exist) == 0 {
|
||||||
|
return util.NewValidationError("no path to check for existence specified")
|
||||||
|
}
|
||||||
|
for idx, val := range c.Exist {
|
||||||
|
val = strings.TrimSpace(val)
|
||||||
|
if val == "" {
|
||||||
|
return util.NewValidationError("invalid path to check for existence")
|
||||||
|
}
|
||||||
|
c.Exist[idx] = util.CleanPath(val)
|
||||||
|
}
|
||||||
|
c.Exist = util.RemoveDuplicates(c.Exist, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *EventActionFilesystemConfig) validate() error {
|
func (c *EventActionFilesystemConfig) validate() error {
|
||||||
if !isFilesystemActionValid(c.Type) {
|
if !isFilesystemActionValid(c.Type) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid filesystem action type: %d", c.Type))
|
return util.NewValidationError(fmt.Sprintf("invalid filesystem action type: %d", c.Type))
|
||||||
|
@ -432,37 +489,31 @@ func (c *EventActionFilesystemConfig) validate() error {
|
||||||
case FilesystemActionRename:
|
case FilesystemActionRename:
|
||||||
c.MkDirs = nil
|
c.MkDirs = nil
|
||||||
c.Deletes = nil
|
c.Deletes = nil
|
||||||
|
c.Exist = nil
|
||||||
if err := c.validateRenames(); err != nil {
|
if err := c.validateRenames(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case FilesystemActionDelete:
|
case FilesystemActionDelete:
|
||||||
c.Renames = nil
|
c.Renames = nil
|
||||||
c.MkDirs = nil
|
c.MkDirs = nil
|
||||||
if len(c.Deletes) == 0 {
|
c.Exist = nil
|
||||||
return util.NewValidationError("no item to delete specified")
|
if err := c.validateDeletes(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
for idx, val := range c.Deletes {
|
|
||||||
val = strings.TrimSpace(val)
|
|
||||||
if val == "" {
|
|
||||||
return util.NewValidationError("invalid item to delete")
|
|
||||||
}
|
|
||||||
c.Deletes[idx] = util.CleanPath(val)
|
|
||||||
}
|
|
||||||
c.Deletes = util.RemoveDuplicates(c.Deletes, false)
|
|
||||||
case FilesystemActionMkdirs:
|
case FilesystemActionMkdirs:
|
||||||
c.Renames = nil
|
c.Renames = nil
|
||||||
c.Deletes = nil
|
c.Deletes = nil
|
||||||
if len(c.MkDirs) == 0 {
|
c.Exist = nil
|
||||||
return util.NewValidationError("no directory to create specified")
|
if err := c.validateMkdirs(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
for idx, val := range c.MkDirs {
|
case FilesystemActionExist:
|
||||||
val = strings.TrimSpace(val)
|
c.Renames = nil
|
||||||
if val == "" {
|
c.Deletes = nil
|
||||||
return util.NewValidationError("invalid directory to create")
|
c.MkDirs = nil
|
||||||
}
|
if err := c.validateExist(); err != nil {
|
||||||
c.MkDirs[idx] = util.CleanPath(val)
|
return err
|
||||||
}
|
}
|
||||||
c.MkDirs = util.RemoveDuplicates(c.MkDirs, false)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -472,12 +523,15 @@ func (c *EventActionFilesystemConfig) getACopy() EventActionFilesystemConfig {
|
||||||
copy(mkdirs, c.MkDirs)
|
copy(mkdirs, c.MkDirs)
|
||||||
deletes := make([]string, len(c.Deletes))
|
deletes := make([]string, len(c.Deletes))
|
||||||
copy(deletes, c.Deletes)
|
copy(deletes, c.Deletes)
|
||||||
|
exist := make([]string, len(c.Exist))
|
||||||
|
copy(exist, c.Exist)
|
||||||
|
|
||||||
return EventActionFilesystemConfig{
|
return EventActionFilesystemConfig{
|
||||||
Type: c.Type,
|
Type: c.Type,
|
||||||
Renames: cloneKeyValues(c.Renames),
|
Renames: cloneKeyValues(c.Renames),
|
||||||
MkDirs: mkdirs,
|
MkDirs: mkdirs,
|
||||||
Deletes: deletes,
|
Deletes: deletes,
|
||||||
|
Exist: exist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1598,7 +1598,7 @@ func TestEventActionValidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, string(resp), "no items to rename specified")
|
assert.Contains(t, string(resp), "no path to rename specified")
|
||||||
action.Options.FsConfig.Renames = []dataprovider.KeyValue{
|
action.Options.FsConfig.Renames = []dataprovider.KeyValue{
|
||||||
{
|
{
|
||||||
Key: "",
|
Key: "",
|
||||||
|
@ -1607,7 +1607,7 @@ func TestEventActionValidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, string(resp), "invalid items to rename")
|
assert.Contains(t, string(resp), "invalid paths to rename")
|
||||||
action.Options.FsConfig.Renames = []dataprovider.KeyValue{
|
action.Options.FsConfig.Renames = []dataprovider.KeyValue{
|
||||||
{
|
{
|
||||||
Key: "adir",
|
Key: "adir",
|
||||||
|
@ -1637,11 +1637,19 @@ func TestEventActionValidation(t *testing.T) {
|
||||||
action.Options.FsConfig.Type = dataprovider.FilesystemActionDelete
|
action.Options.FsConfig.Type = dataprovider.FilesystemActionDelete
|
||||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, string(resp), "no item to delete specified")
|
assert.Contains(t, string(resp), "no path to delete specified")
|
||||||
action.Options.FsConfig.Deletes = []string{"item1", ""}
|
action.Options.FsConfig.Deletes = []string{"item1", ""}
|
||||||
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, string(resp), "invalid item to delete")
|
assert.Contains(t, string(resp), "invalid path to delete")
|
||||||
|
action.Options.FsConfig.Type = dataprovider.FilesystemActionExist
|
||||||
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, string(resp), "no path to check for existence specified")
|
||||||
|
action.Options.FsConfig.Exist = []string{"item1", ""}
|
||||||
|
_, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, string(resp), "invalid path to check for existence")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEventRuleValidation(t *testing.T) {
|
func TestEventRuleValidation(t *testing.T) {
|
||||||
|
@ -18906,6 +18914,34 @@ func TestWebEventAction(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action.Options.FsConfig = dataprovider.EventActionFilesystemConfig{
|
||||||
|
Type: dataprovider.FilesystemActionExist,
|
||||||
|
Exist: []string{"b ", " c/d"},
|
||||||
|
}
|
||||||
|
form.Set("fs_action_type", fmt.Sprintf("%d", action.Options.FsConfig.Type))
|
||||||
|
form.Set("fs_exist_paths", strings.Join(action.Options.FsConfig.Exist, ","))
|
||||||
|
req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
|
||||||
|
bytes.NewBuffer([]byte(form.Encode())))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
setJWTCookieForReq(req, webToken)
|
||||||
|
rr = executeRequest(req)
|
||||||
|
checkResponseCode(t, http.StatusSeeOther, rr)
|
||||||
|
// check the update
|
||||||
|
actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, action.Type, actionGet.Type)
|
||||||
|
if assert.Len(t, actionGet.Options.FsConfig.Exist, 2) {
|
||||||
|
for _, p := range actionGet.Options.FsConfig.Exist {
|
||||||
|
switch p {
|
||||||
|
case "/b":
|
||||||
|
case "/c/d":
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected path %v", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil)
|
req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
setBearerForReq(req, apiToken)
|
setBearerForReq(req, apiToken)
|
||||||
|
@ -18929,7 +18965,13 @@ func TestWebEventRule(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
a := dataprovider.BaseEventAction{
|
a := dataprovider.BaseEventAction{
|
||||||
Name: "web_action",
|
Name: "web_action",
|
||||||
Type: dataprovider.ActionTypeBackup,
|
Type: dataprovider.ActionTypeFilesystem,
|
||||||
|
Options: dataprovider.BaseEventActionOptions{
|
||||||
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||||
|
Type: dataprovider.FilesystemActionExist,
|
||||||
|
Exist: []string{"/dir1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
action, _, err := httpdtest.AddEventAction(a, http.StatusCreated)
|
action, _, err := httpdtest.AddEventAction(a, http.StatusCreated)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -1919,6 +1919,7 @@ func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEven
|
||||||
Renames: getKeyValsFromPostFields(r, "fs_rename_source", "fs_rename_target"),
|
Renames: getKeyValsFromPostFields(r, "fs_rename_source", "fs_rename_target"),
|
||||||
Deletes: strings.Split(strings.ReplaceAll(r.Form.Get("fs_delete_paths"), " ", ""), ","),
|
Deletes: strings.Split(strings.ReplaceAll(r.Form.Get("fs_delete_paths"), " ", ""), ","),
|
||||||
MkDirs: strings.Split(strings.ReplaceAll(r.Form.Get("fs_mkdir_paths"), " ", ""), ","),
|
MkDirs: strings.Split(strings.ReplaceAll(r.Form.Get("fs_mkdir_paths"), " ", ""), ","),
|
||||||
|
Exist: strings.Split(strings.ReplaceAll(r.Form.Get("fs_exist_paths"), " ", ""), ","),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return options, nil
|
return options, nil
|
||||||
|
|
|
@ -2270,6 +2270,14 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF
|
||||||
return errors.New("fs mkdir content mismatch")
|
return errors.New("fs mkdir content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(expected.Exist) != len(actual.Exist) {
|
||||||
|
return errors.New("fs exist mismatch")
|
||||||
|
}
|
||||||
|
for _, v := range expected.Exist {
|
||||||
|
if !util.Contains(actual.Exist, v) {
|
||||||
|
return errors.New("fs exist content mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4356,6 +4356,19 @@ components:
|
||||||
* `7` - Transfer quota reset
|
* `7` - Transfer quota reset
|
||||||
* `8` - Data retention check
|
* `8` - Data retention check
|
||||||
* `9` - Filesystem
|
* `9` - Filesystem
|
||||||
|
FilesystemActionTypes:
|
||||||
|
type: integer
|
||||||
|
enum:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
description: |
|
||||||
|
Supported filesystem action types:
|
||||||
|
* `1` - Rename
|
||||||
|
* `2` - Delete
|
||||||
|
* `3` - Mkdis
|
||||||
|
* `4` - Exist
|
||||||
EventTriggerTypes:
|
EventTriggerTypes:
|
||||||
type: integer
|
type: integer
|
||||||
enum:
|
enum:
|
||||||
|
@ -6057,6 +6070,27 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/FolderRetention'
|
$ref: '#/components/schemas/FolderRetention'
|
||||||
|
EventActionFilesystemConfig:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/FilesystemActionTypes'
|
||||||
|
renames:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/KeyValue'
|
||||||
|
mkdirs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
deletes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
exist:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
BaseEventActionOptions:
|
BaseEventActionOptions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -6068,6 +6102,8 @@ components:
|
||||||
$ref: '#/components/schemas/EventActionEmailConfig'
|
$ref: '#/components/schemas/EventActionEmailConfig'
|
||||||
retention_config:
|
retention_config:
|
||||||
$ref: '#/components/schemas/EventActionDataRetentionConfig'
|
$ref: '#/components/schemas/EventActionDataRetentionConfig'
|
||||||
|
fs_config:
|
||||||
|
$ref: '#/components/schemas/EventActionFilesystemConfig'
|
||||||
BaseEventAction:
|
BaseEventAction:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -487,6 +487,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row action-type action-fs-type action-fs-exist">
|
||||||
|
<label for="idFsExist" class="col-sm-2 col-form-label">Paths</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea class="form-control" id="idFsExist" name="fs_exist_paths" rows="2"
|
||||||
|
aria-describedby="fsExistHelpBlock">{{.Action.Options.FsConfig.GetExistAsString}}</textarea>
|
||||||
|
<small id="fsExistHelpBlock" class="form-text text-muted">
|
||||||
|
Comma separated paths to check for existence as seen by SFTPGo users. Placeholders are supported. The required permissions are granted automatically
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||||
<div class="col-sm-12 text-right px-0">
|
<div class="col-sm-12 text-right px-0">
|
||||||
<button type="submit" class="btn btn-primary mt-3 ml-3 px-5" name="form_action" value="submit">Submit</button>
|
<button type="submit" class="btn btn-primary mt-3 ml-3 px-5" name="form_action" value="submit">Submit</button>
|
||||||
|
@ -748,6 +759,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
case 3:
|
case 3:
|
||||||
$('.action-fs-mkdir').show();
|
$('.action-fs-mkdir').show();
|
||||||
break;
|
break;
|
||||||
|
case '4':
|
||||||
|
case 4:
|
||||||
|
$('.action-fs-exist').show();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue