mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
eventmanager: allow to execute fs actions based on schedules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
2b463d61e3
commit
57935f585c
7 changed files with 298 additions and 85 deletions
|
@ -45,7 +45,7 @@ The following trigger events are supported:
|
|||
|
||||
You can further restrict a rule by specifying additional conditions that must be met before the rule’s actions are taken. For example you can react to uploads only if they are performed by a particular user or using a specified protocol.
|
||||
|
||||
Actions such as user quota reset, transfer quota reset, data retention check and folder quota reset are executed for all matching users if the trigger is a schedule or for the affected user if the trigger is a provider event or a filesystem action.
|
||||
Actions such as user quota reset, transfer quota reset, data retention check, folder quota reset and filesystem events are executed for all matching users if the trigger is a schedule or for the affected user if the trigger is a provider event or a filesystem action.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -59,6 +59,5 @@ Some actions are not supported for some triggers, rules containing incompatible
|
|||
|
||||
- `Filesystem events`, folder quota reset cannot be executed, we don't have a direct way to get the affected folder.
|
||||
- `Provider events`, user quota reset, transfer quota reset, data retention check and filesystem actions can be executed only if we modify a user. They will be executed for the affected user. Folder quota reset can be executed only for folders. Filesystem actions are not executed for `delete` user events because the actions is executed after the user deletion.
|
||||
- `Schedules`, filesystem actions cannot be executed, they require a user.
|
||||
- `IP Blocked`, user quota reset, folder quota reset, transfer quota reset, data retention check and filesystem actions cannot be executed, we only have an IP.
|
||||
- `Certificate`, user quota reset, folder quota reset, transfer quota reset, data retention check and filesystem actions cannot be executed.
|
||||
|
|
18
go.mod
18
go.mod
|
@ -15,13 +15,13 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.12
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.18
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.15
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
||||
github.com/fclairamb/ftpserverlib v0.19.0
|
||||
github.com/fclairamb/go-log v0.4.0
|
||||
github.com/fclairamb/go-log v0.4.1
|
||||
github.com/go-acme/lego/v4 v4.8.0
|
||||
github.com/go-chi/chi/v5 v5.0.8-0.20220512131524-9e71a0d4b3d6
|
||||
github.com/go-chi/jwtauth/v5 v5.0.2
|
||||
|
@ -32,7 +32,7 @@ require (
|
|||
github.com/google/uuid v1.3.0
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/hashicorp/go-hclog v1.2.2
|
||||
github.com/hashicorp/go-plugin v1.4.4
|
||||
github.com/hashicorp/go-plugin v1.4.5
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
|
||||
github.com/klauspost/compress v1.15.9
|
||||
|
@ -68,15 +68,15 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/net v0.0.0-20220811182439-13a9a731de15
|
||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
|
||||
google.golang.org/api v0.92.0
|
||||
google.golang.org/api v0.93.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.103.0 // indirect
|
||||
cloud.google.com/go/compute v1.8.0 // indirect
|
||||
cloud.google.com/go/compute v1.9.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
|
@ -99,12 +99,12 @@ require (
|
|||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-test/deep v1.0.8 // indirect
|
||||
github.com/goccy/go-json v0.9.10 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
|
@ -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-20220815135757-37a418bb8959 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
|
35
go.sum
35
go.sum
|
@ -48,8 +48,8 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
|
|||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/compute v1.8.0 h1:NLtR56/eKx9K1s2Tw/4hec2vsU1S3WeKRMj8HXbBo6E=
|
||||
cloud.google.com/go/compute v1.8.0/go.mod h1:boQ44qJsMqZjKzzsEkoJWQGj4h8ygmyk17UArClWzmg=
|
||||
cloud.google.com/go/compute v1.9.0 h1:ED/FP4xv8GJw63v556/ASNc1CeeLUO2Bs8nzaHchkHg=
|
||||
cloud.google.com/go/compute v1.9.0/go.mod h1:lWv1h/zUWTm/LozzfTJhBSkd6ShQq8la8VeeuOEGxfY=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
|
@ -191,8 +191,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z
|
|||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 h1:h9qqTedYnA9JcWjKyLV6UYIMSdp91ExLCUbjbpDLH7A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5/go.mod h1:J8SS5Tp/zeLxaubB0xGfKnVrvssNBNLwTipreTKLhjQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17 h1:x4JtJ0TaVVCoNc3bUtv0W5VvMLFiQ1++ReiRfSxRYf8=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.17/go.mod h1:HvF8QZUW+evBsd/SJn4VA0WWW5qVMKxPpWiRRK4w3eM=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.18 h1:OEPeoMWuUp1SvUvrLMh8B7SJPRz6M1hP/AV4pmXybx4=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.18/go.mod h1:HvF8QZUW+evBsd/SJn4VA0WWW5qVMKxPpWiRRK4w3eM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
|
||||
|
@ -254,8 +254,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
|
||||
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
|
@ -286,8 +286,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
|||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fclairamb/ftpserverlib v0.19.0 h1:5QcSQ0OIJBlezIqmGehiL/AVsRb6dIkMxbkuhyPkESM=
|
||||
github.com/fclairamb/ftpserverlib v0.19.0/go.mod h1:pmukdVOFKKUY9zjWRoxFW8JAljyulC/uK5FfusJzK2E=
|
||||
github.com/fclairamb/go-log v0.4.0 h1:HLm0yU9IzNCqayuTqtLyWUy/Bjud7+DZWTSg0lAC5pQ=
|
||||
github.com/fclairamb/go-log v0.4.0/go.mod h1:sw1KvnkZ4wKCYkvy4SL3qVZcJSWFP8Ure4pM3z+KNn4=
|
||||
github.com/fclairamb/go-log v0.4.1 h1:rLtdSG9x2pK41AIAnE8WYpl05xBJfw1ZyYxZaXFcBsM=
|
||||
github.com/fclairamb/go-log v0.4.1/go.mod h1:sw1KvnkZ4wKCYkvy4SL3qVZcJSWFP8Ure4pM3z+KNn4=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
|
@ -338,8 +338,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
|||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
|
||||
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
|
@ -465,8 +465,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S
|
|||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M=
|
||||
github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ=
|
||||
github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
|
||||
github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo=
|
||||
github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -976,8 +976,9 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -1120,8 +1121,8 @@ google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3
|
|||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
|
||||
google.golang.org/api v0.86.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
|
||||
google.golang.org/api v0.92.0 h1:8JHk7q/+rJla+iRsWj9FQ9/wjv2M1SKtpKSdmLhxPT0=
|
||||
google.golang.org/api v0.92.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
|
||||
google.golang.org/api v0.93.0 h1:T2xt9gi0gHdxdnRkVQhT8mIvPaXKNsDNWz+L696M66M=
|
||||
google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -1228,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-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-20220815135757-37a418bb8959 h1:hw4Y42zL1VyVKxPgRHHh191fpVBGV8sNVmcow5Z8VXY=
|
||||
google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
||||
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 h1:C2UVWqrgLYKrT5nh5oU6hLRm1AeEklCK5eloQA1NtFY=
|
||||
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/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=
|
||||
|
|
|
@ -628,8 +628,8 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params EventP
|
|||
return err
|
||||
}
|
||||
|
||||
func getUserForEventAction(username string) (dataprovider.User, error) {
|
||||
user, err := dataprovider.GetUserWithGroupSettings(username)
|
||||
func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
|
||||
err := user.LoadAndApplyGroupSettings()
|
||||
if err != nil {
|
||||
return dataprovider.User{}, err
|
||||
}
|
||||
|
@ -649,8 +649,8 @@ func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileIn
|
|||
return conn.RemoveFile(fs, fsPath, item, info)
|
||||
}
|
||||
|
||||
func executeDeleteFsAction(deletes []string, replacer *strings.Replacer, username string) error {
|
||||
user, err := getUserForEventAction(username)
|
||||
func executeDeleteFsActionForUser(deletes []string, replacer *strings.Replacer, user dataprovider.User) error {
|
||||
user, err := getUserForEventAction(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -684,8 +684,40 @@ func executeDeleteFsAction(deletes []string, replacer *strings.Replacer, usernam
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeMkDirsFsAction(dirs []string, replacer *strings.Replacer, username string) error {
|
||||
user, err := getUserForEventAction(username)
|
||||
func executeDeleteFsRuleAction(deletes []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 delete for user %s, name conditions don't match",
|
||||
user.Username)
|
||||
continue
|
||||
}
|
||||
executed++
|
||||
if err = executeDeleteFsActionForUser(deletes, replacer, user); err != nil {
|
||||
failures = append(failures, user.Username)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return fmt.Errorf("fs delete failed for users: %+v", failures)
|
||||
}
|
||||
if executed == 0 {
|
||||
eventManagerLog(logger.LevelError, "no delete executed")
|
||||
return errors.New("no delete executed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeMkDirsFsActionForUser(dirs []string, replacer *strings.Replacer, user dataprovider.User) error {
|
||||
user, err := getUserForEventAction(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -709,8 +741,42 @@ func executeMkDirsFsAction(dirs []string, replacer *strings.Replacer, username s
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeRenameFsAction(renames []dataprovider.KeyValue, replacer *strings.Replacer, username string) error {
|
||||
user, err := getUserForEventAction(username)
|
||||
func executeMkdirFsRuleAction(dirs []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 mkdir for user %s, name conditions don't match",
|
||||
user.Username)
|
||||
continue
|
||||
}
|
||||
executed++
|
||||
if err = executeMkDirsFsActionForUser(dirs, replacer, user); err != nil {
|
||||
failures = append(failures, user.Username)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return fmt.Errorf("fs mkdir failed for users: %+v", failures)
|
||||
}
|
||||
if executed == 0 {
|
||||
eventManagerLog(logger.LevelError, "no mkdir executed")
|
||||
return errors.New("no mkdir executed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeRenameFsActionForUser(renames []dataprovider.KeyValue, replacer *strings.Replacer,
|
||||
user dataprovider.User,
|
||||
) error {
|
||||
user, err := getUserForEventAction(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -732,17 +798,51 @@ func executeRenameFsAction(renames []dataprovider.KeyValue, replacer *strings.Re
|
|||
return nil
|
||||
}
|
||||
|
||||
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, params EventParams) error {
|
||||
func executeRenameFsRuleAction(renames []dataprovider.KeyValue, 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 rename for user %s, name conditions don't match",
|
||||
user.Username)
|
||||
continue
|
||||
}
|
||||
executed++
|
||||
if err = executeRenameFsActionForUser(renames, replacer, user); err != nil {
|
||||
failures = append(failures, user.Username)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return fmt.Errorf("fs rename failed for users: %+v", failures)
|
||||
}
|
||||
if executed == 0 {
|
||||
eventManagerLog(logger.LevelError, "no rename executed")
|
||||
return errors.New("no rename executed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions dataprovider.ConditionOptions,
|
||||
params EventParams,
|
||||
) error {
|
||||
addObjectData := false
|
||||
replacements := params.getStringReplacements(addObjectData)
|
||||
replacer := strings.NewReplacer(replacements...)
|
||||
switch c.Type {
|
||||
case dataprovider.FilesystemActionRename:
|
||||
return executeRenameFsAction(c.Renames, replacer, params.sender)
|
||||
return executeRenameFsRuleAction(c.Renames, replacer, conditions, params)
|
||||
case dataprovider.FilesystemActionDelete:
|
||||
return executeDeleteFsAction(c.Deletes, replacer, params.sender)
|
||||
return executeDeleteFsRuleAction(c.Deletes, replacer, conditions, params)
|
||||
case dataprovider.FilesystemActionMkdirs:
|
||||
return executeMkDirsFsAction(c.MkDirs, replacer, params.sender)
|
||||
return executeMkdirFsRuleAction(c.MkDirs, replacer, conditions, params)
|
||||
default:
|
||||
return fmt.Errorf("unsupported filesystem action %d", c.Type)
|
||||
}
|
||||
|
@ -953,7 +1053,7 @@ func executeRuleAction(action dataprovider.BaseEventAction, params EventParams,
|
|||
case dataprovider.ActionTypeDataRetentionCheck:
|
||||
return executeDataRetentionCheckRuleAction(action.Options.RetentionConfig, conditions, params)
|
||||
case dataprovider.ActionTypeFilesystem:
|
||||
return executeFsRuleAction(action.Options.FsConfig, params)
|
||||
return executeFsRuleAction(action.Options.FsConfig, conditions, params)
|
||||
default:
|
||||
return fmt.Errorf("unsupported action type: %d", action.Type)
|
||||
}
|
||||
|
@ -1070,10 +1170,6 @@ func (j *eventCronJob) Run() {
|
|||
eventManagerLog(logger.LevelError, "unable to load rule with name %q", j.ruleName)
|
||||
return
|
||||
}
|
||||
if err = rule.CheckActionsConsistency(""); err != nil {
|
||||
eventManagerLog(logger.LevelWarn, "scheduled rule %q skipped: %v", rule.Name, err)
|
||||
return
|
||||
}
|
||||
task, err := j.getTask(rule)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -275,10 +275,18 @@ func TestEventManagerErrors(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
err = executeTransferQuotaResetRuleAction(dataprovider.ConditionOptions{}, EventParams{})
|
||||
assert.Error(t, err)
|
||||
err = executeDeleteFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
|
||||
assert.Error(t, err)
|
||||
err = executeMkdirFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
|
||||
assert.Error(t, err)
|
||||
err = executeRenameFsRuleAction(nil, nil, dataprovider.ConditionOptions{}, EventParams{})
|
||||
assert.Error(t, err)
|
||||
|
||||
groupName := "agroup"
|
||||
err = executeQuotaResetForUser(dataprovider.User{
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: "agroup",
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
|
@ -287,12 +295,39 @@ func TestEventManagerErrors(t *testing.T) {
|
|||
err = executeDataRetentionCheckForUser(dataprovider.User{
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: "agroup",
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
assert.Error(t, err)
|
||||
err = executeDeleteFsActionForUser(nil, nil, dataprovider.User{
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
err = executeMkDirsFsActionForUser(nil, nil, dataprovider.User{
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
err = executeRenameFsActionForUser(nil, nil, dataprovider.User{
|
||||
Groups: []sdk.GroupMapping{
|
||||
{
|
||||
Name: groupName,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
dataRetentionAction := dataprovider.BaseEventAction{
|
||||
Type: dataprovider.ActionTypeDataRetentionCheck,
|
||||
|
@ -633,6 +668,60 @@ func TestEventRuleActions(t *testing.T) {
|
|||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "no transfer quota reset executed")
|
||||
}
|
||||
action.Type = dataprovider.ActionTypeFilesystem
|
||||
action.Options = dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionRename,
|
||||
Renames: []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "/source",
|
||||
Value: "/target",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "no match",
|
||||
},
|
||||
},
|
||||
})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "no rename executed")
|
||||
}
|
||||
action.Options = dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionDelete,
|
||||
Deletes: []string{"/dir1"},
|
||||
},
|
||||
}
|
||||
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "no match",
|
||||
},
|
||||
},
|
||||
})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "no delete executed")
|
||||
}
|
||||
action.Options = dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionMkdirs,
|
||||
Deletes: []string{"/dir1"},
|
||||
},
|
||||
}
|
||||
err = executeRuleAction(action, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "no match",
|
||||
},
|
||||
},
|
||||
})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "no mkdir executed")
|
||||
}
|
||||
|
||||
err = dataprovider.DeleteUser(username1, "", "")
|
||||
assert.NoError(t, err)
|
||||
|
@ -712,19 +801,12 @@ func TestEventRuleActions(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFilesystemActionErrors(t *testing.T) {
|
||||
err := executeFsRuleAction(dataprovider.EventActionFilesystemConfig{}, EventParams{})
|
||||
err := executeFsRuleAction(dataprovider.EventActionFilesystemConfig{}, dataprovider.ConditionOptions{}, EventParams{})
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unsupported filesystem action")
|
||||
}
|
||||
username := "test_user_for_actions"
|
||||
testReplacer := strings.NewReplacer("old", "new")
|
||||
err = executeDeleteFsAction(nil, testReplacer, username)
|
||||
assert.Error(t, err)
|
||||
err = executeMkDirsFsAction(nil, testReplacer, username)
|
||||
assert.Error(t, err)
|
||||
err = executeRenameFsAction(nil, testReplacer, username)
|
||||
assert.Error(t, err)
|
||||
|
||||
user := dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
Username: username,
|
||||
|
@ -750,11 +832,11 @@ func TestFilesystemActionErrors(t *testing.T) {
|
|||
err = dataprovider.AddUser(&user, "", "")
|
||||
assert.NoError(t, err)
|
||||
// check root fs fails
|
||||
err = executeDeleteFsAction(nil, testReplacer, username)
|
||||
err = executeDeleteFsActionForUser(nil, testReplacer, user)
|
||||
assert.Error(t, err)
|
||||
err = executeMkDirsFsAction(nil, testReplacer, username)
|
||||
err = executeMkDirsFsActionForUser(nil, testReplacer, user)
|
||||
assert.Error(t, err)
|
||||
err = executeRenameFsAction(nil, testReplacer, username)
|
||||
err = executeRenameFsActionForUser(nil, testReplacer, user)
|
||||
assert.Error(t, err)
|
||||
|
||||
user.FsConfig.Provider = sdk.LocalFilesystemProvider
|
||||
|
@ -763,15 +845,36 @@ func TestFilesystemActionErrors(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = dataprovider.AddUser(&user, "", "")
|
||||
assert.NoError(t, err)
|
||||
err = executeRenameFsAction([]dataprovider.KeyValue{
|
||||
err = executeRenameFsActionForUser([]dataprovider.KeyValue{
|
||||
{
|
||||
Key: "/p1",
|
||||
Value: "/p1",
|
||||
},
|
||||
}, testReplacer, username)
|
||||
}, testReplacer, user)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "the rename source and target cannot be the same")
|
||||
}
|
||||
err = executeRuleAction(dataprovider.BaseEventAction{
|
||||
Type: dataprovider.ActionTypeFilesystem,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionRename,
|
||||
Renames: []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "/p2",
|
||||
Value: "/p2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: username,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
if runtime.GOOS != osWindows {
|
||||
dirPath := filepath.Join(user.HomeDir, "adir", "sub")
|
||||
|
@ -783,26 +886,59 @@ func TestFilesystemActionErrors(t *testing.T) {
|
|||
err = os.Chmod(dirPath, 0001)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = executeDeleteFsAction([]string{"/adir/sub"}, testReplacer, username)
|
||||
err = executeDeleteFsActionForUser([]string{"/adir/sub"}, testReplacer, user)
|
||||
assert.Error(t, err)
|
||||
err = executeDeleteFsAction([]string{"/adir/sub/f.dat"}, testReplacer, username)
|
||||
err = executeDeleteFsActionForUser([]string{"/adir/sub/f.dat"}, testReplacer, user)
|
||||
assert.Error(t, err)
|
||||
err = os.Chmod(dirPath, 0555)
|
||||
assert.NoError(t, err)
|
||||
err = executeDeleteFsAction([]string{"/adir/sub/f.dat"}, testReplacer, username)
|
||||
err = executeDeleteFsActionForUser([]string{"/adir/sub/f.dat"}, testReplacer, user)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unable to remove file")
|
||||
}
|
||||
err = executeRuleAction(dataprovider.BaseEventAction{
|
||||
Type: dataprovider.ActionTypeFilesystem,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionDelete,
|
||||
Deletes: []string{"/adir/sub/f.dat"},
|
||||
},
|
||||
},
|
||||
}, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: username,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
err = executeMkDirsFsAction([]string{"/adir/sub/sub"}, testReplacer, username)
|
||||
err = executeMkDirsFsActionForUser([]string{"/adir/sub/sub"}, testReplacer, user)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unable to create dir")
|
||||
}
|
||||
err = executeMkDirsFsAction([]string{"/adir/sub/sub/sub"}, testReplacer, username)
|
||||
err = executeMkDirsFsActionForUser([]string{"/adir/sub/sub/sub"}, testReplacer, user)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unable to check parent dirs")
|
||||
}
|
||||
|
||||
err = executeRuleAction(dataprovider.BaseEventAction{
|
||||
Type: dataprovider.ActionTypeFilesystem,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionMkdirs,
|
||||
MkDirs: []string{"/adir/sub/sub1"},
|
||||
},
|
||||
},
|
||||
}, EventParams{}, dataprovider.ConditionOptions{
|
||||
Names: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: username,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
err = os.Chmod(dirPath, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -944,17 +1080,6 @@ func TestScheduledActions(t *testing.T) {
|
|||
job.Run()
|
||||
assert.DirExists(t, backupsPath)
|
||||
|
||||
action.Type = dataprovider.ActionTypeFilesystem
|
||||
action.Options = dataprovider.BaseEventActionOptions{
|
||||
FsConfig: dataprovider.EventActionFilesystemConfig{
|
||||
Type: dataprovider.FilesystemActionMkdirs,
|
||||
MkDirs: []string{"/dir"},
|
||||
},
|
||||
}
|
||||
err = dataprovider.UpdateEventAction(action, "", "")
|
||||
assert.NoError(t, err)
|
||||
job.Run() // action is not compatible with a scheduled rule
|
||||
|
||||
err = dataprovider.DeleteEventRule(rule.Name, "", "")
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.DeleteEventAction(action.Name, "", "")
|
||||
|
|
|
@ -1062,14 +1062,6 @@ func (r *EventRule) CheckActionsConsistency(providerObjectType string) error {
|
|||
action.Name, getActionTypeAsString(action.Type))
|
||||
}
|
||||
}
|
||||
case EventTriggerSchedule:
|
||||
// to execute a filesystem action we need a user
|
||||
for _, action := range r.Actions {
|
||||
if action.Type == ActionTypeFilesystem {
|
||||
return fmt.Errorf("action %q, type %q is not supported for scheduled events",
|
||||
action.Name, getActionTypeAsString(action.Type))
|
||||
}
|
||||
}
|
||||
case EventTriggerIPBlocked, EventTriggerCertificate:
|
||||
if err := r.checkIPBlockedAndCertificateActions(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
NFPM_VERSION=2.17.0
|
||||
NFPM_VERSION=2.18.1
|
||||
NFPM_ARCH=${NFPM_ARCH:-amd64}
|
||||
if [ -z ${SFTPGO_VERSION} ]
|
||||
then
|
||||
|
|
Loading…
Reference in a new issue