diff --git a/Cargo.lock b/Cargo.lock index 1e169b51..615d2bfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,9 +175,9 @@ checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -557,7 +557,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -597,7 +597,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -608,13 +608,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -649,6 +649,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -663,7 +672,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -674,9 +683,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483020b893cdef3d89637e428d588650c71cfae7ea2e6ecbaee4de4ff99fb2dd" +checksum = "c478f5b10ce55c9a33f87ca3404ca92768b144fc1bfdede7c0121214a8283a25" dependencies = [ "aws-credential-types", "aws-runtime", @@ -764,9 +773,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.101.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b16efa59a199f5271bf21ab3e570c5297d819ce4f240e6cf0096d1dc0049c44" +checksum = "75ddb925e840f49446aa6338b67abdbec04b4ebf923b7da038ec4c35afb916cd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -798,9 +807,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.79.0" +version = "1.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a847168f15b46329fa32c7aca4e4f1a2e072f9b422f0adb19756f2e1457f111" +checksum = "79ede098271e3471036c46957cba2ba30888f53bda2515bf04b560614a30a36e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -820,9 +829,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.80.0" +version = "1.82.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b654dd24d65568738593e8239aef279a86a15374ec926ae8714e2d7245f34149" +checksum = "43326f724ba2cc957e6f3deac0ca1621a3e5d4146f5970c24c8a108dac33070f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -842,9 +851,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.81.0" +version = "1.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92ea8a7602321c83615c82b408820ad54280fb026e92de0eeea937342fafa24" +checksum = "a5468593c47efc31fdbe6c902d1a5fde8d9c82f78a3f8ccfe907b1e9434748cb" dependencies = [ "aws-credential-types", "aws-runtime", @@ -924,9 +933,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.10" +version = "0.60.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604c7aec361252b8f1c871a7641d5e0ba3a7f5a586e51b66bc9510a5519594d9" +checksum = "182b03393e8c677347fb5705a04a9392695d47d20ef0a2f8cfe28c8e6b9b9778" dependencies = [ "aws-smithy-types", "bytes", @@ -956,9 +965,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" +checksum = "4fdbad9bd9dbcc6c5e68c311a841b54b70def3ca3b674c42fbebb265980539f8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -969,7 +978,7 @@ dependencies = [ "http 1.3.1", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.24.2", "hyper-rustls 0.27.7", "hyper-util", @@ -979,6 +988,7 @@ dependencies = [ "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", + "tokio-rustls 0.26.2", "tower", "tracing", ] @@ -1013,9 +1023,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.6" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796" +checksum = "a3d57c8b53a72d15c8e190475743acf34e4996685e346a3448dd54ef696fc6e0" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1037,9 +1047,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.7" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d52251ed4b9776a3e8487b2a01ac915f73b2da3af8fc1e77e0fce697a550d4" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1114,7 +1124,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "itoa 1.0.15", "matchit", @@ -1232,7 +1242,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cexpr", "clang-sys", "itertools 0.12.1", @@ -1245,7 +1255,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.104", + "syn 2.0.106", "which", ] @@ -1257,9 +1267,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" dependencies = [ "serde", ] @@ -1325,7 +1335,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" dependencies = [ - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -1354,9 +1364,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1399,6 +1409,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" + [[package]] name = "byteorder" version = "1.5.0" @@ -1464,7 +1480,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cairo-sys-rs", "glib", "libc", @@ -1511,7 +1527,7 @@ dependencies = [ "serde", "serde-untagged", "serde-value", - "thiserror 2.0.14", + "thiserror 2.0.15", "toml", "unicode-xid", "url", @@ -1529,7 +1545,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.15", ] [[package]] @@ -1540,9 +1556,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.32" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "jobserver", "libc", @@ -1697,9 +1713,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.44" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", "clap_derive", @@ -1719,14 +1735,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1766,7 +1782,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block", "cocoa-foundation 0.2.1", "core-foundation 0.10.1", @@ -1796,7 +1812,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0", @@ -1898,7 +1914,7 @@ checksum = "04382d0d9df7434af6b1b49ea1a026ef39df1b0738b1cc373368cf175354f6eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1918,7 +1934,7 @@ checksum = "95013972663dd72254b963e48857284080001ffee418731f065fcf5290a5530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2007,7 +2023,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types", @@ -2031,7 +2047,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation 0.10.1", "libc", ] @@ -2249,7 +2265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2294,12 +2310,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b136475da5ef7b6ac596c0e956e37bad51b85b987ff3d5e230e964936736b2" +checksum = "08440b3dd222c3d0433e63e097463969485f112baff337dfdaca043a0d760570" dependencies = [ - "darling_core 0.21.1", - "darling_macro 0.21.1", + "darling_core 0.21.2", + "darling_macro 0.21.2", ] [[package]] @@ -2313,21 +2329,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "darling_core" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44ad32f92b75fb438b04b68547e521a548be8acc339a6dacc4a7121488f53e6" +checksum = "d25b7912bc28a04ab1b7715a68ea03aaa15662b43a1a4b2c480531fd19f8bf7e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2338,18 +2354,18 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "darling_macro" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5be8a7a562d315a5b92a630c30cec6bcf663e6673f00fbb69cca66a6f521b9" +checksum = "ce154b9bea7fb0c8e8326e62d00354000c36e79770ff21b8c84e3aa267d9d531" dependencies = [ - "darling_core 0.21.1", + "darling_core 0.21.2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2740,7 +2756,7 @@ checksum = "4800e1ff7ecf8f310887e9b54c9c444b8e215ccbc7b21c2f244cfae373b1ece7" dependencies = [ "datafusion-expr", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2927,13 +2943,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2954,7 +2970,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2964,7 +2980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2977,7 +2993,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3086,7 +3102,7 @@ dependencies = [ "dioxus-rsx", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3280,7 +3296,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3373,7 +3389,7 @@ dependencies = [ "proc-macro2", "quote", "slab", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3385,7 +3401,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3446,7 +3462,7 @@ dependencies = [ "proc-macro2", "quote", "server_fn_macro", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3482,10 +3498,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.6.1", "libc", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -3496,7 +3512,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3528,7 +3544,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3656,7 +3672,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3677,28 +3693,28 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "enumset" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ee17054f550fd7400e1906e2f9356c7672643ed34008a9e8abe147ccd2d821" +checksum = "f50acec76c668b4621fe3694e5ddee53c8fae2a03410026f50947d195eb44dc1" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d07902c93376f1e96c34abc4d507c0911df3816cef50b01f5a2ff3ad8c370d" +checksum = "588eaef9dbc5d72c5fa4edf162527e67dab5d14ba61519ef7c8e233d5bdc092c" dependencies = [ - "darling 0.20.11", + "darling 0.21.2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3793,6 +3809,20 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "serde_json", + "uncased", + "version_check", +] + [[package]] name = "filetime" version = "0.2.25" @@ -3833,7 +3863,7 @@ version = "25.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "rustc_version", ] @@ -3862,7 +3892,7 @@ dependencies = [ "regex", "serde", "serde_derive", - "thiserror 2.0.14", + "thiserror 2.0.15", "toml", "tracing", "tracing-subscriber", @@ -3909,7 +3939,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4021,7 +4051,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4274,7 +4304,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "futures-channel", "futures-core", "futures-executor", @@ -4302,7 +4332,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4327,7 +4357,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b436093d1598b05e3b7fddc097b2bad32763f53a1beb25ab6f9718c6a60acd09" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cocoa 0.25.0", "crossbeam-channel", "keyboard-types", @@ -4467,7 +4497,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4736,13 +4766,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", @@ -4750,6 +4781,7 @@ dependencies = [ "httpdate", "itoa 1.0.15", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -4778,7 +4810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "log", "rustls 0.23.31", @@ -4796,7 +4828,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "pin-project-lite", "tokio", @@ -4816,7 +4848,7 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", @@ -5011,13 +5043,19 @@ dependencies = [ "cfb", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "inotify-sys", "libc", ] @@ -5061,7 +5099,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cfg-if", "libc", ] @@ -5237,7 +5275,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "serde", "unicode-segmentation", ] @@ -5446,7 +5484,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "libc", "redox_syscall 0.5.17", ] @@ -5465,7 +5503,7 @@ dependencies = [ "once_cell", "serde", "sha2 0.10.9", - "thiserror 2.0.14", + "thiserror 2.0.15", "uuid", ] @@ -5526,7 +5564,7 @@ checksum = "656b3b27f8893f7bbf9485148ff9a65f019e3f33bd5cdc87c83cab16b3fd9ec8" dependencies = [ "libc", "neli", - "thiserror 2.0.14", + "thiserror 2.0.15", "windows-sys 0.59.0", ] @@ -5668,7 +5706,7 @@ dependencies = [ "manganis-core", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5831,7 +5869,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "jni-sys", "log", "ndk-sys", @@ -5902,7 +5940,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cfg-if", "cfg_aliases", "libc", @@ -5915,7 +5953,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cfg-if", "cfg_aliases", "libc", @@ -5962,7 +6000,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "fsevent-sys", "inotify", "kqueue", @@ -6155,7 +6193,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6179,7 +6217,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d5c6c0ef9702176a570f06ad94f3198bc29c524c8b498f1b9346e1b1bdcbb3a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "libloading 0.8.8", "nvml-wrapper-sys", "static_assertions", @@ -6235,9 +6273,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -6248,7 +6286,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -6264,9 +6302,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.6.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", "objc2-foundation 0.3.1", ] @@ -6277,7 +6315,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -6289,9 +6327,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -6300,7 +6338,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "objc2-core-foundation", ] @@ -6328,7 +6366,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -6340,9 +6378,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.6.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", ] @@ -6362,7 +6400,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -6374,7 +6412,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -6469,7 +6507,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.14", + "thiserror 2.0.15", "tracing", ] @@ -6498,7 +6536,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost 0.13.5", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tonic 0.13.1", "tracing", @@ -6546,7 +6584,7 @@ dependencies = [ "percent-encoding", "rand 0.9.2", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tokio-stream", ] @@ -6740,6 +6778,29 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.106", +] + [[package]] name = "pem" version = "3.0.5" @@ -6921,7 +6982,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7116,12 +7177,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7184,9 +7245,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -7199,8 +7260,9 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "version_check", + "yansi", ] [[package]] @@ -7241,7 +7303,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.104", + "syn 2.0.106", "tempfile", ] @@ -7255,7 +7317,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7268,7 +7330,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7295,7 +7357,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "memchr", "unicase", ] @@ -7344,7 +7406,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls 0.23.31", "socket2 0.5.10", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tracing", "web-time", @@ -7365,7 +7427,7 @@ dependencies = [ "rustls 0.23.31", "rustls-pki-types", "slab", - "thiserror 2.0.14", + "thiserror 2.0.15", "tinyvec", "tracing", "web-time", @@ -7524,9 +7586,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -7534,9 +7596,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -7595,7 +7657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7613,7 +7675,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", ] [[package]] @@ -7624,7 +7686,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.14", + "thiserror 2.0.15", ] [[package]] @@ -7654,7 +7716,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7709,9 +7771,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -7723,7 +7785,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -7798,7 +7860,7 @@ dependencies = [ "dispatch2", "js-sys", "log", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -7840,7 +7902,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tokio-util", "tracing", @@ -7852,11 +7914,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad9720d9d2a943779f1dc3d47fa9072c7eeffaff4e1a82f67eb9f7ea52696091" dependencies = [ - "darling 0.21.1", + "darling 0.21.2", "proc-macro2", "quote", "serde_json", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7940,7 +8002,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.104", + "syn 2.0.106", "walkdir", ] @@ -7982,7 +8044,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8051,7 +8113,7 @@ dependencies = [ "futures", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "libsystemd", "matchit", @@ -8063,6 +8125,7 @@ dependencies = [ "rust-embed", "rustfs-ahm", "rustfs-appauth", + "rustfs-audit-logger", "rustfs-common", "rustfs-config", "rustfs-ecstore", @@ -8076,6 +8139,7 @@ dependencies = [ "rustfs-rio", "rustfs-s3select-api", "rustfs-s3select-query", + "rustfs-targets", "rustfs-utils", "rustfs-zip", "rustls 0.23.31", @@ -8086,7 +8150,7 @@ dependencies = [ "shadow-rs", "socket2 0.6.0", "sysctl", - "thiserror 2.0.14", + "thiserror 2.0.15", "tikv-jemallocator", "time", "tokio", @@ -8123,7 +8187,7 @@ dependencies = [ "serde_json", "serial_test", "tempfile", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tokio-util", "tracing", @@ -8143,6 +8207,25 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustfs-audit-logger" +version = "0.0.5" +dependencies = [ + "async-trait", + "chrono", + "figment", + "reqwest", + "rustfs-targets", + "serde", + "serde_json", + "thiserror 2.0.15", + "tokio", + "tracing", + "tracing-core", + "url", + "uuid", +] + [[package]] name = "rustfs-checksums" version = "0.0.5" @@ -8196,7 +8279,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "test-case", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", ] @@ -8222,7 +8305,7 @@ dependencies = [ "hex-simd", "hmac 0.12.1", "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-util", "lazy_static", @@ -8262,7 +8345,7 @@ dependencies = [ "smallvec", "temp-env", "tempfile", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "tokio", "tokio-stream", @@ -8290,7 +8373,7 @@ dependencies = [ "rustfs-utils", "s3s", "serde", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "tokio", "tracing", @@ -8335,7 +8418,7 @@ dependencies = [ "rustfs-utils", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "tokio", "tracing", @@ -8352,7 +8435,7 @@ dependencies = [ "rustfs-protos", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tonic 0.14.1", "tracing", @@ -8366,7 +8449,7 @@ version = "0.0.5" dependencies = [ "chrono", "humantime", - "hyper 1.6.0", + "hyper 1.7.0", "serde", "serde_json", "time", @@ -8401,21 +8484,18 @@ dependencies = [ "futures", "once_cell", "quick-xml 0.38.1", - "reqwest", "rumqttc", "rustfs-config", "rustfs-ecstore", + "rustfs-targets", "rustfs-utils", "serde", "serde_json", - "snap", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tracing", "tracing-subscriber", "url", - "urlencoding", - "uuid", "wildmatch", ] @@ -8442,7 +8522,7 @@ dependencies = [ "serde_json", "smallvec", "sysinfo", - "thiserror 2.0.14", + "thiserror 2.0.15", "tokio", "tracing", "tracing-core", @@ -8465,7 +8545,7 @@ dependencies = [ "serde_json", "strum", "test-case", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "tokio", ] @@ -8519,7 +8599,7 @@ dependencies = [ "futures-util", "hex", "hmac 0.12.1", - "hyper 1.6.0", + "hyper 1.7.0", "md5", "once_cell", "regex", @@ -8577,7 +8657,7 @@ version = "0.0.5" dependencies = [ "bytes", "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "rustfs-utils", "s3s", "serde_urlencoded", @@ -8585,13 +8665,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "rustfs-targets" +version = "0.0.5" +dependencies = [ + "async-trait", + "reqwest", + "rumqttc", + "rustfs-config", + "rustfs-utils", + "serde", + "serde_json", + "snap", + "thiserror 2.0.15", + "tokio", + "tracing", + "url", + "urlencoding", + "uuid", +] + [[package]] name = "rustfs-utils" version = "0.0.5" dependencies = [ "base64-simd", "blake3", - "brotli 8.0.1", + "brotli 8.0.2", "bytes", "crc32fast", "flate2", @@ -8599,7 +8699,7 @@ dependencies = [ "hex-simd", "highway", "hmac 0.12.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "local-ip-address", "lz4", @@ -8652,7 +8752,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -8665,7 +8765,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.9.4", @@ -8848,7 +8948,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "httparse", - "hyper 1.6.0", + "hyper 1.7.0", "itoa 1.0.15", "md-5", "memchr", @@ -8865,7 +8965,7 @@ dependencies = [ "smallvec", "std-next", "sync_wrapper", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "tokio", "tower", @@ -8886,9 +8986,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ "sdd", ] @@ -8925,7 +9025,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8976,7 +9076,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -8989,7 +9089,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -9061,9 +9161,9 @@ dependencies = [ [[package]] name = "serde-untagged" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +checksum = "34836a629bcbc6f1afdf0907a744870039b1e14c0561cb26094fa683b158eff3" dependencies = [ "erased-serde", "serde", @@ -9111,7 +9211,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9122,7 +9222,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9136,9 +9236,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa 1.0.15", "memchr", @@ -9175,7 +9275,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9234,7 +9334,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9276,7 +9376,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "xxhash-rust", ] @@ -9287,7 +9387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" dependencies = [ "server_fn_macro", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9441,7 +9541,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", ] @@ -9480,7 +9580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f62f06db0370222f7f498ef478fce9f8df5828848d1d3517e3331936d7074f55" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9530,7 +9630,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9633,7 +9733,7 @@ checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9668,7 +9768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04082e93ed1a06debd9148c928234b46d2cf260bc65f44e1d1d3fa594c5beebc" dependencies = [ "simdutf8", - "thiserror 2.0.14", + "thiserror 2.0.15", ] [[package]] @@ -9720,7 +9820,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9820,9 +9920,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -9846,7 +9946,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9855,7 +9955,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "byteorder", "enum-as-inner", "libc", @@ -9883,7 +9983,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9917,7 +10017,7 @@ version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6682a07cf5bab0b8a2bd20d0a542917ab928b5edb75ebd4eda6b05cbaab872da" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "cocoa 0.26.1", "core-foundation 0.10.1", "core-graphics 0.24.0", @@ -9959,7 +10059,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10019,7 +10119,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10030,7 +10130,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "test-case-core", ] @@ -10051,11 +10151,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" dependencies = [ - "thiserror-impl 2.0.14", + "thiserror-impl 2.0.15", ] [[package]] @@ -10066,18 +10166,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10184,9 +10284,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -10226,7 +10326,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10389,7 +10489,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -10418,7 +10518,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -10442,7 +10542,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10467,7 +10567,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.104", + "syn 2.0.106", "tempfile", "tonic-build", ] @@ -10498,7 +10598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", - "bitflags 2.9.1", + "bitflags 2.9.2", "bytes", "futures-core", "futures-util", @@ -10559,7 +10659,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10673,14 +10773,14 @@ dependencies = [ "dirs", "libappindicator", "muda 0.15.3", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.1", "once_cell", "png", - "thiserror 2.0.14", + "thiserror 2.0.15", "windows-sys 0.59.0", ] @@ -10784,6 +10884,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.8.1" @@ -10894,7 +11003,7 @@ checksum = "22b7ad00068276db5fea436dba78daa7891b8d60db76e4f51cbdefbdecdab97e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11001,7 +11110,7 @@ checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11047,7 +11156,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -11082,7 +11191,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -11129,7 +11238,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -11141,7 +11250,7 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "wayland-backend", "wayland-client", "wayland-scanner", @@ -11281,7 +11390,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11424,7 +11533,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11435,7 +11544,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11446,7 +11555,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11457,7 +11566,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11854,7 +11963,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", ] [[package]] @@ -11866,7 +11975,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12009,7 +12118,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -12083,7 +12192,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils 2.1.0", ] @@ -12096,7 +12205,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zbus_names 4.2.0", "zvariant 5.6.0", "zvariant_utils 3.2.0", @@ -12142,7 +12251,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12162,7 +12271,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -12183,7 +12292,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12216,7 +12325,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12241,7 +12350,7 @@ dependencies = [ "memchr", "pbkdf2", "sha1 0.10.6", - "thiserror 2.0.14", + "thiserror 2.0.15", "time", "xz2", "zeroize", @@ -12327,7 +12436,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils 2.1.0", ] @@ -12340,7 +12449,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils 3.2.0", ] @@ -12352,7 +12461,7 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12365,6 +12474,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.104", + "syn 2.0.106", "winnow 0.7.12", ] diff --git a/Cargo.toml b/Cargo.toml index e01b6731..63383960 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "rustfs", # Core file system implementation "cli/rustfs-gui", # Graphical user interface client "crates/appauth", # Application authentication and authorization + "crates/audit-logger", # Audit logging system for file operations "crates/common", # Shared utilities and data structures "crates/config", # Configuration management "crates/crypto", # Cryptography and security features @@ -30,6 +31,7 @@ members = [ "crates/obs", # Observability utilities "crates/protos", # Protocol buffer definitions "crates/rio", # Rust I/O utilities and abstractions + "crates/targets", # Target-specific configurations and utilities "crates/s3select-api", # S3 Select API interface "crates/s3select-query", # S3 Select query engine "crates/signer", # client signer @@ -37,7 +39,7 @@ members = [ "crates/utils", # Utility functions and helpers "crates/workers", # Worker thread pools and task scheduling "crates/zip", # ZIP file handling and compression - "crates/ahm", + "crates/ahm", # Asynchronous Hash Map for concurrent data structures "crates/mcp", # MCP server for S3 operations ] resolver = "2" @@ -59,15 +61,11 @@ unsafe_code = "deny" [workspace.lints.clippy] all = "warn" -[patch.crates-io] -rustfs-utils = { path = "crates/utils" } -rustfs-filemeta = { path = "crates/filemeta" } -rustfs-rio = { path = "crates/rio" } - [workspace.dependencies] rustfs-ahm = { path = "crates/ahm", version = "0.0.5" } rustfs-s3select-api = { path = "crates/s3select-api", version = "0.0.5" } rustfs-appauth = { path = "crates/appauth", version = "0.0.5" } +rustfs-audit-logger = { path = "crates/audit-logger", version = "0.0.5" } rustfs-common = { path = "crates/common", version = "0.0.5" } rustfs-crypto = { path = "crates/crypto", version = "0.0.5" } rustfs-ecstore = { path = "crates/ecstore", version = "0.0.5" } @@ -89,6 +87,7 @@ rustfs-signer = { path = "crates/signer", version = "0.0.5" } rustfs-checksums = { path = "crates/checksums", version = "0.0.5" } rustfs-workers = { path = "crates/workers", version = "0.0.5" } rustfs-mcp = { path = "crates/mcp", version = "0.0.5" } +rustfs-targets = { path = "crates/targets", version = "0.0.5" } aes-gcm = { version = "0.10.3", features = ["std"] } anyhow = "1.0.99" arc-swap = "1.7.1" @@ -96,15 +95,15 @@ argon2 = { version = "0.5.3", features = ["std"] } atoi = "2.0.0" async-channel = "2.5.0" async-recursion = "1.1.1" -async-trait = "0.1.88" +async-trait = "0.1.89" async-compression = { version = "0.4.19" } atomic_enum = "0.3.0" -aws-config = { version = "1.8.4" } +aws-config = { version = "1.8.5" } aws-sdk-s3 = "1.101.0" axum = "0.8.4" base64-simd = "0.8.0" base64 = "0.22.1" -brotli = "8.0.1" +brotli = "8.0.2" bytes = { version = "1.10.1", features = ["serde"] } bytesize = "2.0.1" byteorder = "1.5.0" @@ -112,7 +111,7 @@ cfg-if = "1.0.1" crc-fast = "1.4.0" chacha20poly1305 = { version = "0.10.1" } chrono = { version = "0.4.41", features = ["serde"] } -clap = { version = "4.5.44", features = ["derive", "env"] } +clap = { version = "4.5.45", features = ["derive", "env"] } const-str = { version = "0.6.4", features = ["std", "proc"] } crc32fast = "1.5.0" criterion = { version = "0.7", features = ["html_reports"] } @@ -121,7 +120,7 @@ datafusion = "46.0.1" derive_builder = "0.20.2" dioxus = { version = "0.6.3", features = ["router"] } dirs = "6.0.0" -enumset = "1.1.7" +enumset = "1.1.9" flatbuffers = "25.2.10" flate2 = "1.1.2" flexi_logger = { version = "0.31.2", features = ["trc", "dont_minimize_extra_stacks"] } @@ -134,7 +133,7 @@ hex = "0.4.3" hex-simd = "0.8.0" highway = { version = "1.3.0" } hmac = "0.12.1" -hyper = "1.6.0" +hyper = "1.7.0" hyper-util = { version = "0.1.16", features = [ "tokio", "server-auto", @@ -193,7 +192,7 @@ rand = "0.9.2" rdkafka = { version = "0.38.0", features = ["tokio"] } reed-solomon-simd = { version = "3.0.1" } regex = { version = "1.11.1" } -reqwest = { version = "0.12.22", default-features = false, features = [ +reqwest = { version = "0.12.23", default-features = false, features = [ "rustls-tls", "charset", "http2", @@ -220,7 +219,7 @@ rustls-pemfile = "2.2.0" s3s = { version = "0.12.0-minio-preview.3" } schemars = "1.0.4" serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.142", features = ["raw_value"] } +serde_json = { version = "1.0.143", features = ["raw_value"] } serde_urlencoded = "0.7.1" serial_test = "3.2.0" sha1 = "0.10.6" @@ -237,7 +236,7 @@ sysctl = "0.6.0" tempfile = "3.20.0" temp-env = "0.3.6" test-case = "3.3.1" -thiserror = "2.0.14" +thiserror = "2.0.15" time = { version = "0.3.41", features = [ "std", "parsing", @@ -278,7 +277,7 @@ zstd = "0.13.3" [workspace.metadata.cargo-shear] -ignored = ["rustfs", "rust-i18n", "rustfs-mcp"] +ignored = ["rustfs", "rust-i18n", "rustfs-mcp", "rustfs-audit-logger", "tokio-test"] [profile.wasm-dev] inherits = "dev" diff --git a/crates/audit-logger/Cargo.toml b/crates/audit-logger/Cargo.toml new file mode 100644 index 00000000..640ea3b3 --- /dev/null +++ b/crates/audit-logger/Cargo.toml @@ -0,0 +1,44 @@ +# Copyright 2024 RustFS Team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[package] +name = "rustfs-audit-logger" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true +homepage.workspace = true +description = "Audit logging system for RustFS, providing detailed logging of file operations and system events." +documentation = "https://docs.rs/audit-logger/latest/audit_logger/" +keywords = ["audit", "logging", "file-operations", "system-events", "RustFS"] +categories = ["web-programming", "development-tools::profiling", "asynchronous", "api-bindings", "development-tools::debugging"] + +[dependencies] +rustfs-targets = { workspace = true } +async-trait = { workspace = true } +chrono = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true, features = ["std", "attributes"] } +tracing-core = { workspace = true } +tokio = { workspace = true, features = ["sync", "fs", "rt-multi-thread", "rt", "time", "macros"] } +url = { workspace = true } +uuid = { workspace = true } +thiserror = { workspace = true } +figment = { version = "0.10", features = ["json", "env"] } + +[lints] +workspace = true diff --git a/crates/audit-logger/examples/config.json b/crates/audit-logger/examples/config.json new file mode 100644 index 00000000..329f26ad --- /dev/null +++ b/crates/audit-logger/examples/config.json @@ -0,0 +1,34 @@ +{ + "console": { + "enabled": true + }, + "logger_webhook": { + "default": { + "enabled": true, + "endpoint": "http://localhost:3000/logs", + "auth_token": "secret-token-for-logs", + "batch_size": 5, + "queue_size": 1000, + "max_retry": 3, + "retry_interval": "2s" + } + }, + "audit_webhook": { + "splunk": { + "enabled": true, + "endpoint": "http://localhost:3000/audit", + "auth_token": "secret-token-for-audit", + "batch_size": 10 + } + }, + "audit_kafka": { + "default": { + "enabled": false, + "brokers": [ + "kafka1:9092", + "kafka2:9092" + ], + "topic": "minio-audit-events" + } + } +} \ No newline at end of file diff --git a/crates/audit-logger/examples/main.rs b/crates/audit-logger/examples/main.rs new file mode 100644 index 00000000..7cc4a0b7 --- /dev/null +++ b/crates/audit-logger/examples/main.rs @@ -0,0 +1,17 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +fn main() { + println!("Audit Logger Example"); +} diff --git a/crates/audit-logger/src/entry/args.rs b/crates/audit-logger/src/entry/args.rs new file mode 100644 index 00000000..1ac65af1 --- /dev/null +++ b/crates/audit-logger/src/entry/args.rs @@ -0,0 +1,90 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use crate::entry::ObjectVersion; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Args - defines the arguments for API operations +/// Args is used to define the arguments for API operations. +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::Args; +/// use std::collections::HashMap; +/// +/// let args = Args::new() +/// .set_bucket(Some("my-bucket".to_string())) +/// .set_object(Some("my-object".to_string())) +/// .set_version_id(Some("123".to_string())) +/// .set_metadata(Some(HashMap::new())); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] +pub struct Args { + #[serde(rename = "bucket", skip_serializing_if = "Option::is_none")] + pub bucket: Option, + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "versionId", skip_serializing_if = "Option::is_none")] + pub version_id: Option, + #[serde(rename = "objects", skip_serializing_if = "Option::is_none")] + pub objects: Option>, + #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] + pub metadata: Option>, +} + +impl Args { + /// Create a new Args object + pub fn new() -> Self { + Args { + bucket: None, + object: None, + version_id: None, + objects: None, + metadata: None, + } + } + + /// Set the bucket + pub fn set_bucket(mut self, bucket: Option) -> Self { + self.bucket = bucket; + self + } + + /// Set the object + pub fn set_object(mut self, object: Option) -> Self { + self.object = object; + self + } + + /// Set the version ID + pub fn set_version_id(mut self, version_id: Option) -> Self { + self.version_id = version_id; + self + } + + /// Set the objects + pub fn set_objects(mut self, objects: Option>) -> Self { + self.objects = objects; + self + } + + /// Set the metadata + pub fn set_metadata(mut self, metadata: Option>) -> Self { + self.metadata = metadata; + self + } +} diff --git a/crates/audit-logger/src/entry/audit.rs b/crates/audit-logger/src/entry/audit.rs new file mode 100644 index 00000000..ad41b363 --- /dev/null +++ b/crates/audit-logger/src/entry/audit.rs @@ -0,0 +1,469 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use crate::{BaseLogEntry, LogRecord, ObjectVersion}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; + +/// API details structure +/// ApiDetails is used to define the details of an API operation +/// +/// The `ApiDetails` structure contains the following fields: +/// - `name` - the name of the API operation +/// - `bucket` - the bucket name +/// - `object` - the object name +/// - `objects` - the list of objects +/// - `status` - the status of the API operation +/// - `status_code` - the status code of the API operation +/// - `input_bytes` - the input bytes +/// - `output_bytes` - the output bytes +/// - `header_bytes` - the header bytes +/// - `time_to_first_byte` - the time to first byte +/// - `time_to_first_byte_in_ns` - the time to first byte in nanoseconds +/// - `time_to_response` - the time to response +/// - `time_to_response_in_ns` - the time to response in nanoseconds +/// +/// The `ApiDetails` structure contains the following methods: +/// - `new` - create a new `ApiDetails` with default values +/// - `set_name` - set the name +/// - `set_bucket` - set the bucket +/// - `set_object` - set the object +/// - `set_objects` - set the objects +/// - `set_status` - set the status +/// - `set_status_code` - set the status code +/// - `set_input_bytes` - set the input bytes +/// - `set_output_bytes` - set the output bytes +/// - `set_header_bytes` - set the header bytes +/// - `set_time_to_first_byte` - set the time to first byte +/// - `set_time_to_first_byte_in_ns` - set the time to first byte in nanoseconds +/// - `set_time_to_response` - set the time to response +/// - `set_time_to_response_in_ns` - set the time to response in nanoseconds +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::ApiDetails; +/// use rustfs_audit_logger::ObjectVersion; +/// +/// let api = ApiDetails::new() +/// .set_name(Some("GET".to_string())) +/// .set_bucket(Some("my-bucket".to_string())) +/// .set_object(Some("my-object".to_string())) +/// .set_objects(vec![ObjectVersion::new_with_object_name("my-object".to_string())]) +/// .set_status(Some("OK".to_string())) +/// .set_status_code(Some(200)) +/// .set_input_bytes(100) +/// .set_output_bytes(200) +/// .set_header_bytes(Some(50)) +/// .set_time_to_first_byte(Some("100ms".to_string())) +/// .set_time_to_first_byte_in_ns(Some("100000000ns".to_string())) +/// .set_time_to_response(Some("200ms".to_string())) +/// .set_time_to_response_in_ns(Some("200000000ns".to_string())); +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] +pub struct ApiDetails { + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "bucket", skip_serializing_if = "Option::is_none")] + pub bucket: Option, + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "objects", skip_serializing_if = "Vec::is_empty", default)] + pub objects: Vec, + #[serde(rename = "status", skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "statusCode", skip_serializing_if = "Option::is_none")] + pub status_code: Option, + #[serde(rename = "rx")] + pub input_bytes: i64, + #[serde(rename = "tx")] + pub output_bytes: i64, + #[serde(rename = "txHeaders", skip_serializing_if = "Option::is_none")] + pub header_bytes: Option, + #[serde(rename = "timeToFirstByte", skip_serializing_if = "Option::is_none")] + pub time_to_first_byte: Option, + #[serde(rename = "timeToFirstByteInNS", skip_serializing_if = "Option::is_none")] + pub time_to_first_byte_in_ns: Option, + #[serde(rename = "timeToResponse", skip_serializing_if = "Option::is_none")] + pub time_to_response: Option, + #[serde(rename = "timeToResponseInNS", skip_serializing_if = "Option::is_none")] + pub time_to_response_in_ns: Option, +} + +impl ApiDetails { + /// Create a new `ApiDetails` with default values + pub fn new() -> Self { + ApiDetails { + name: None, + bucket: None, + object: None, + objects: Vec::new(), + status: None, + status_code: None, + input_bytes: 0, + output_bytes: 0, + header_bytes: None, + time_to_first_byte: None, + time_to_first_byte_in_ns: None, + time_to_response: None, + time_to_response_in_ns: None, + } + } + + /// Set the name + pub fn set_name(mut self, name: Option) -> Self { + self.name = name; + self + } + + /// Set the bucket + pub fn set_bucket(mut self, bucket: Option) -> Self { + self.bucket = bucket; + self + } + + /// Set the object + pub fn set_object(mut self, object: Option) -> Self { + self.object = object; + self + } + + /// Set the objects + pub fn set_objects(mut self, objects: Vec) -> Self { + self.objects = objects; + self + } + + /// Set the status + pub fn set_status(mut self, status: Option) -> Self { + self.status = status; + self + } + + /// Set the status code + pub fn set_status_code(mut self, status_code: Option) -> Self { + self.status_code = status_code; + self + } + + /// Set the input bytes + pub fn set_input_bytes(mut self, input_bytes: i64) -> Self { + self.input_bytes = input_bytes; + self + } + + /// Set the output bytes + pub fn set_output_bytes(mut self, output_bytes: i64) -> Self { + self.output_bytes = output_bytes; + self + } + + /// Set the header bytes + pub fn set_header_bytes(mut self, header_bytes: Option) -> Self { + self.header_bytes = header_bytes; + self + } + + /// Set the time to first byte + pub fn set_time_to_first_byte(mut self, time_to_first_byte: Option) -> Self { + self.time_to_first_byte = time_to_first_byte; + self + } + + /// Set the time to first byte in nanoseconds + pub fn set_time_to_first_byte_in_ns(mut self, time_to_first_byte_in_ns: Option) -> Self { + self.time_to_first_byte_in_ns = time_to_first_byte_in_ns; + self + } + + /// Set the time to response + pub fn set_time_to_response(mut self, time_to_response: Option) -> Self { + self.time_to_response = time_to_response; + self + } + + /// Set the time to response in nanoseconds + pub fn set_time_to_response_in_ns(mut self, time_to_response_in_ns: Option) -> Self { + self.time_to_response_in_ns = time_to_response_in_ns; + self + } +} + +/// Entry - audit entry logs +/// AuditLogEntry is used to define the structure of an audit log entry +/// +/// The `AuditLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `version` - the version of the audit log entry +/// - `deployment_id` - the deployment ID +/// - `event` - the event +/// - `entry_type` - the type of audit message +/// - `api` - the API details +/// - `remote_host` - the remote host +/// - `user_agent` - the user agent +/// - `req_path` - the request path +/// - `req_host` - the request host +/// - `req_claims` - the request claims +/// - `req_query` - the request query +/// - `req_header` - the request header +/// - `resp_header` - the response header +/// - `access_key` - the access key +/// - `parent_user` - the parent user +/// - `error` - the error +/// +/// The `AuditLogEntry` structure contains the following methods: +/// - `new` - create a new `AuditEntry` with default values +/// - `new_with_values` - create a new `AuditEntry` with version, time, event and api details +/// - `with_base` - set the base log entry +/// - `set_version` - set the version +/// - `set_deployment_id` - set the deployment ID +/// - `set_event` - set the event +/// - `set_entry_type` - set the entry type +/// - `set_api` - set the API details +/// - `set_remote_host` - set the remote host +/// - `set_user_agent` - set the user agent +/// - `set_req_path` - set the request path +/// - `set_req_host` - set the request host +/// - `set_req_claims` - set the request claims +/// - `set_req_query` - set the request query +/// - `set_req_header` - set the request header +/// - `set_resp_header` - set the response header +/// - `set_access_key` - set the access key +/// - `set_parent_user` - set the parent user +/// - `set_error` - set the error +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::AuditLogEntry; +/// use rustfs_audit_logger::ApiDetails; +/// use std::collections::HashMap; +/// +/// let entry = AuditLogEntry::new() +/// .set_version("1.0".to_string()) +/// .set_deployment_id(Some("123".to_string())) +/// .set_event("event".to_string()) +/// .set_entry_type(Some("type".to_string())) +/// .set_api(ApiDetails::new()) +/// .set_remote_host(Some("remote-host".to_string())) +/// .set_user_agent(Some("user-agent".to_string())) +/// .set_req_path(Some("req-path".to_string())) +/// .set_req_host(Some("req-host".to_string())) +/// .set_req_claims(Some(HashMap::new())) +/// .set_req_query(Some(HashMap::new())) +/// .set_req_header(Some(HashMap::new())) +/// .set_resp_header(Some(HashMap::new())) +/// .set_access_key(Some("access-key".to_string())) +/// .set_parent_user(Some("parent-user".to_string())) +/// .set_error(Some("error".to_string())); +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct AuditLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + pub version: String, + #[serde(rename = "deploymentid", skip_serializing_if = "Option::is_none")] + pub deployment_id: Option, + pub event: String, + // Class of audit message - S3, admin ops, bucket management + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub entry_type: Option, + pub api: ApiDetails, + #[serde(rename = "remotehost", skip_serializing_if = "Option::is_none")] + pub remote_host: Option, + #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")] + pub user_agent: Option, + #[serde(rename = "requestPath", skip_serializing_if = "Option::is_none")] + pub req_path: Option, + #[serde(rename = "requestHost", skip_serializing_if = "Option::is_none")] + pub req_host: Option, + #[serde(rename = "requestClaims", skip_serializing_if = "Option::is_none")] + pub req_claims: Option>, + #[serde(rename = "requestQuery", skip_serializing_if = "Option::is_none")] + pub req_query: Option>, + #[serde(rename = "requestHeader", skip_serializing_if = "Option::is_none")] + pub req_header: Option>, + #[serde(rename = "responseHeader", skip_serializing_if = "Option::is_none")] + pub resp_header: Option>, + #[serde(rename = "accessKey", skip_serializing_if = "Option::is_none")] + pub access_key: Option, + #[serde(rename = "parentUser", skip_serializing_if = "Option::is_none")] + pub parent_user: Option, + #[serde(rename = "error", skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +impl AuditLogEntry { + /// Create a new `AuditEntry` with default values + pub fn new() -> Self { + AuditLogEntry { + base: BaseLogEntry::new(), + version: String::new(), + deployment_id: None, + event: String::new(), + entry_type: None, + api: ApiDetails::new(), + remote_host: None, + user_agent: None, + req_path: None, + req_host: None, + req_claims: None, + req_query: None, + req_header: None, + resp_header: None, + access_key: None, + parent_user: None, + error: None, + } + } + + /// Create a new `AuditEntry` with version, time, event and api details + pub fn new_with_values(version: String, time: DateTime, event: String, api: ApiDetails) -> Self { + let mut base = BaseLogEntry::new(); + base.timestamp = time; + + AuditLogEntry { + base, + version, + deployment_id: None, + event, + entry_type: None, + api, + remote_host: None, + user_agent: None, + req_path: None, + req_host: None, + req_claims: None, + req_query: None, + req_header: None, + resp_header: None, + access_key: None, + parent_user: None, + error: None, + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the version + pub fn set_version(mut self, version: String) -> Self { + self.version = version; + self + } + + /// Set the deployment ID + pub fn set_deployment_id(mut self, deployment_id: Option) -> Self { + self.deployment_id = deployment_id; + self + } + + /// Set the event + pub fn set_event(mut self, event: String) -> Self { + self.event = event; + self + } + + /// Set the entry type + pub fn set_entry_type(mut self, entry_type: Option) -> Self { + self.entry_type = entry_type; + self + } + + /// Set the API details + pub fn set_api(mut self, api: ApiDetails) -> Self { + self.api = api; + self + } + + /// Set the remote host + pub fn set_remote_host(mut self, remote_host: Option) -> Self { + self.remote_host = remote_host; + self + } + + /// Set the user agent + pub fn set_user_agent(mut self, user_agent: Option) -> Self { + self.user_agent = user_agent; + self + } + + /// Set the request path + pub fn set_req_path(mut self, req_path: Option) -> Self { + self.req_path = req_path; + self + } + + /// Set the request host + pub fn set_req_host(mut self, req_host: Option) -> Self { + self.req_host = req_host; + self + } + + /// Set the request claims + pub fn set_req_claims(mut self, req_claims: Option>) -> Self { + self.req_claims = req_claims; + self + } + + /// Set the request query + pub fn set_req_query(mut self, req_query: Option>) -> Self { + self.req_query = req_query; + self + } + + /// Set the request header + pub fn set_req_header(mut self, req_header: Option>) -> Self { + self.req_header = req_header; + self + } + + /// Set the response header + pub fn set_resp_header(mut self, resp_header: Option>) -> Self { + self.resp_header = resp_header; + self + } + + /// Set the access key + pub fn set_access_key(mut self, access_key: Option) -> Self { + self.access_key = access_key; + self + } + + /// Set the parent user + pub fn set_parent_user(mut self, parent_user: Option) -> Self { + self.parent_user = parent_user; + self + } + + /// Set the error + pub fn set_error(mut self, error: Option) -> Self { + self.error = error; + self + } +} + +impl LogRecord for AuditLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} diff --git a/crates/audit-logger/src/entry/base.rs b/crates/audit-logger/src/entry/base.rs new file mode 100644 index 00000000..164ef3ef --- /dev/null +++ b/crates/audit-logger/src/entry/base.rs @@ -0,0 +1,108 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; + +/// Base log entry structure shared by all log types +/// This structure is used to serialize log entries to JSON +/// and send them to the log sinks +/// This structure is also used to deserialize log entries from JSON +/// This structure is also used to store log entries in the database +/// This structure is also used to query log entries from the database +/// +/// The `BaseLogEntry` structure contains the following fields: +/// - `timestamp` - the timestamp of the log entry +/// - `request_id` - the request ID of the log entry +/// - `message` - the message of the log entry +/// - `tags` - the tags of the log entry +/// +/// The `BaseLogEntry` structure contains the following methods: +/// - `new` - create a new `BaseLogEntry` with default values +/// - `message` - set the message +/// - `request_id` - set the request ID +/// - `tags` - set the tags +/// - `timestamp` - set the timestamp +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::BaseLogEntry; +/// use chrono::{DateTime, Utc}; +/// use std::collections::HashMap; +/// +/// let timestamp = Utc::now(); +/// let request = Some("req-123".to_string()); +/// let message = Some("This is a log message".to_string()); +/// let tags = Some(HashMap::new()); +/// +/// let entry = BaseLogEntry::new() +/// .timestamp(timestamp) +/// .request_id(request) +/// .message(message) +/// .tags(tags); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Default)] +pub struct BaseLogEntry { + #[serde(rename = "time")] + pub timestamp: DateTime, + + #[serde(rename = "requestID", skip_serializing_if = "Option::is_none")] + pub request_id: Option, + + #[serde(rename = "message", skip_serializing_if = "Option::is_none")] + pub message: Option, + + #[serde(rename = "tags", skip_serializing_if = "Option::is_none")] + pub tags: Option>, +} + +impl BaseLogEntry { + /// Create a new BaseLogEntry with default values + pub fn new() -> Self { + BaseLogEntry { + timestamp: Utc::now(), + request_id: None, + message: None, + tags: None, + } + } + + /// Set the message + pub fn message(mut self, message: Option) -> Self { + self.message = message; + self + } + + /// Set the request ID + pub fn request_id(mut self, request_id: Option) -> Self { + self.request_id = request_id; + self + } + + /// Set the tags + pub fn tags(mut self, tags: Option>) -> Self { + self.tags = tags; + self + } + + /// Set the timestamp + pub fn timestamp(mut self, timestamp: DateTime) -> Self { + self.timestamp = timestamp; + self + } +} diff --git a/crates/audit-logger/src/entry/mod.rs b/crates/audit-logger/src/entry/mod.rs new file mode 100644 index 00000000..4fb7769f --- /dev/null +++ b/crates/audit-logger/src/entry/mod.rs @@ -0,0 +1,159 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] +pub(crate) mod args; +pub(crate) mod audit; +pub(crate) mod base; +pub(crate) mod unified; + +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tracing_core::Level; + +/// ObjectVersion is used across multiple modules +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ObjectVersion { + #[serde(rename = "name")] + pub object_name: String, + #[serde(rename = "versionId", skip_serializing_if = "Option::is_none")] + pub version_id: Option, +} + +impl ObjectVersion { + /// Create a new ObjectVersion object + pub fn new() -> Self { + ObjectVersion { + object_name: String::new(), + version_id: None, + } + } + + /// Create a new ObjectVersion with object name + pub fn new_with_object_name(object_name: String) -> Self { + ObjectVersion { + object_name, + version_id: None, + } + } + + /// Set the object name + pub fn set_object_name(mut self, object_name: String) -> Self { + self.object_name = object_name; + self + } + + /// Set the version ID + pub fn set_version_id(mut self, version_id: Option) -> Self { + self.version_id = version_id; + self + } +} + +impl Default for ObjectVersion { + fn default() -> Self { + Self::new() + } +} + +/// Log kind/level enum +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +pub enum LogKind { + #[serde(rename = "INFO")] + #[default] + Info, + #[serde(rename = "WARNING")] + Warning, + #[serde(rename = "ERROR")] + Error, + #[serde(rename = "FATAL")] + Fatal, +} + +/// Trait for types that can be serialized to JSON and have a timestamp +/// This trait is used by `ServerLogEntry` to convert the log entry to JSON +/// and get the timestamp of the log entry +/// This trait is implemented by `ServerLogEntry` +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::LogRecord; +/// use chrono::{DateTime, Utc}; +/// use rustfs_audit_logger::ServerLogEntry; +/// use tracing_core::Level; +/// +/// let log_entry = ServerLogEntry::new(Level::INFO, "api_handler".to_string()); +/// let json = log_entry.to_json(); +/// let timestamp = log_entry.get_timestamp(); +/// ``` +pub trait LogRecord { + fn to_json(&self) -> String; + fn get_timestamp(&self) -> chrono::DateTime; +} + +/// Wrapper for `tracing_core::Level` to implement `Serialize` and `Deserialize` +/// for `ServerLogEntry` +/// This is necessary because `tracing_core::Level` does not implement `Serialize` +/// and `Deserialize` +/// This is a workaround to allow `ServerLogEntry` to be serialized and deserialized +/// using `serde` +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::SerializableLevel; +/// use tracing_core::Level; +/// +/// let level = Level::INFO; +/// let serializable_level = SerializableLevel::from(level); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SerializableLevel(pub Level); + +impl From for SerializableLevel { + fn from(level: Level) -> Self { + SerializableLevel(level) + } +} + +impl From for Level { + fn from(serializable_level: SerializableLevel) -> Self { + serializable_level.0 + } +} + +impl Serialize for SerializableLevel { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.0.as_str()) + } +} + +impl<'de> Deserialize<'de> for SerializableLevel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "TRACE" => Ok(SerializableLevel(Level::TRACE)), + "DEBUG" => Ok(SerializableLevel(Level::DEBUG)), + "INFO" => Ok(SerializableLevel(Level::INFO)), + "WARN" => Ok(SerializableLevel(Level::WARN)), + "ERROR" => Ok(SerializableLevel(Level::ERROR)), + _ => Err(D::Error::custom("unknown log level")), + } + } +} diff --git a/crates/audit-logger/src/entry/unified.rs b/crates/audit-logger/src/entry/unified.rs new file mode 100644 index 00000000..e9ae3a94 --- /dev/null +++ b/crates/audit-logger/src/entry/unified.rs @@ -0,0 +1,266 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use crate::{AuditLogEntry, BaseLogEntry, LogKind, LogRecord, SerializableLevel}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use tracing_core::Level; + +/// Server log entry with structured fields +/// ServerLogEntry is used to log structured log entries from the server +/// +/// The `ServerLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `level` - the log level +/// - `source` - the source of the log entry +/// - `user_id` - the user ID +/// - `fields` - the structured fields of the log entry +/// +/// The `ServerLogEntry` structure contains the following methods: +/// - `new` - create a new `ServerLogEntry` with specified level and source +/// - `with_base` - set the base log entry +/// - `user_id` - set the user ID +/// - `fields` - set the fields +/// - `add_field` - add a field +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::ServerLogEntry; +/// use tracing_core::Level; +/// +/// let entry = ServerLogEntry::new(Level::INFO, "test_module".to_string()) +/// .user_id(Some("user-456".to_string())) +/// .add_field("operation".to_string(), "login".to_string()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ServerLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + + pub level: SerializableLevel, + pub source: String, + + #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] + pub user_id: Option, + + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub fields: Vec<(String, String)>, +} + +impl ServerLogEntry { + /// Create a new ServerLogEntry with specified level and source + pub fn new(level: Level, source: String) -> Self { + ServerLogEntry { + base: BaseLogEntry::new(), + level: SerializableLevel(level), + source, + user_id: None, + fields: Vec::new(), + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the user ID + pub fn user_id(mut self, user_id: Option) -> Self { + self.user_id = user_id; + self + } + + /// Set fields + pub fn fields(mut self, fields: Vec<(String, String)>) -> Self { + self.fields = fields; + self + } + + /// Add a field + pub fn add_field(mut self, key: String, value: String) -> Self { + self.fields.push((key, value)); + self + } +} + +impl LogRecord for ServerLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} + +/// Console log entry structure +/// ConsoleLogEntry is used to log console log entries +/// The `ConsoleLogEntry` structure contains the following fields: +/// - `base` - the base log entry +/// - `level` - the log level +/// - `console_msg` - the console message +/// - `node_name` - the node name +/// - `err` - the error message +/// +/// The `ConsoleLogEntry` structure contains the following methods: +/// - `new` - create a new `ConsoleLogEntry` +/// - `new_with_console_msg` - create a new `ConsoleLogEntry` with console message and node name +/// - `with_base` - set the base log entry +/// - `set_level` - set the log level +/// - `set_node_name` - set the node name +/// - `set_console_msg` - set the console message +/// - `set_err` - set the error message +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::ConsoleLogEntry; +/// +/// let entry = ConsoleLogEntry::new_with_console_msg("Test message".to_string(), "node-123".to_string()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConsoleLogEntry { + #[serde(flatten)] + pub base: BaseLogEntry, + + pub level: LogKind, + pub console_msg: String, + pub node_name: String, + + #[serde(skip)] + pub err: Option, +} + +impl ConsoleLogEntry { + /// Create a new ConsoleLogEntry + pub fn new() -> Self { + ConsoleLogEntry { + base: BaseLogEntry::new(), + level: LogKind::Info, + console_msg: String::new(), + node_name: String::new(), + err: None, + } + } + + /// Create a new ConsoleLogEntry with console message and node name + pub fn new_with_console_msg(console_msg: String, node_name: String) -> Self { + ConsoleLogEntry { + base: BaseLogEntry::new(), + level: LogKind::Info, + console_msg, + node_name, + err: None, + } + } + + /// Set the base log entry + pub fn with_base(mut self, base: BaseLogEntry) -> Self { + self.base = base; + self + } + + /// Set the log level + pub fn set_level(mut self, level: LogKind) -> Self { + self.level = level; + self + } + + /// Set the node name + pub fn set_node_name(mut self, node_name: String) -> Self { + self.node_name = node_name; + self + } + + /// Set the console message + pub fn set_console_msg(mut self, console_msg: String) -> Self { + self.console_msg = console_msg; + self + } + + /// Set the error message + pub fn set_err(mut self, err: Option) -> Self { + self.err = err; + self + } +} + +impl Default for ConsoleLogEntry { + fn default() -> Self { + Self::new() + } +} + +impl LogRecord for ConsoleLogEntry { + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| String::from("{}")) + } + + fn get_timestamp(&self) -> DateTime { + self.base.timestamp + } +} + +/// Unified log entry type +/// UnifiedLogEntry is used to log different types of log entries +/// +/// The `UnifiedLogEntry` enum contains the following variants: +/// - `Server` - a server log entry +/// - `Audit` - an audit log entry +/// - `Console` - a console log entry +/// +/// The `UnifiedLogEntry` enum contains the following methods: +/// - `to_json` - convert the log entry to JSON +/// - `get_timestamp` - get the timestamp of the log entry +/// +/// # Example +/// ``` +/// use rustfs_audit_logger::{UnifiedLogEntry, ServerLogEntry}; +/// use tracing_core::Level; +/// +/// let server_entry = ServerLogEntry::new(Level::INFO, "test_module".to_string()); +/// let unified = UnifiedLogEntry::Server(server_entry); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum UnifiedLogEntry { + #[serde(rename = "server")] + Server(ServerLogEntry), + + #[serde(rename = "audit")] + Audit(Box), + + #[serde(rename = "console")] + Console(ConsoleLogEntry), +} + +impl LogRecord for UnifiedLogEntry { + fn to_json(&self) -> String { + match self { + UnifiedLogEntry::Server(entry) => entry.to_json(), + UnifiedLogEntry::Audit(entry) => entry.to_json(), + UnifiedLogEntry::Console(entry) => entry.to_json(), + } + } + + fn get_timestamp(&self) -> DateTime { + match self { + UnifiedLogEntry::Server(entry) => entry.get_timestamp(), + UnifiedLogEntry::Audit(entry) => entry.get_timestamp(), + UnifiedLogEntry::Console(entry) => entry.get_timestamp(), + } + } +} diff --git a/crates/audit-logger/src/lib.rs b/crates/audit-logger/src/lib.rs new file mode 100644 index 00000000..afa0422b --- /dev/null +++ b/crates/audit-logger/src/lib.rs @@ -0,0 +1,8 @@ +mod entry; +mod logger; + +pub use entry::args::Args; +pub use entry::audit::{ApiDetails, AuditLogEntry}; +pub use entry::base::BaseLogEntry; +pub use entry::unified::{ConsoleLogEntry, ServerLogEntry, UnifiedLogEntry}; +pub use entry::{LogKind, LogRecord, ObjectVersion, SerializableLevel}; diff --git a/crates/audit-logger/src/logger/config.rs b/crates/audit-logger/src/logger/config.rs new file mode 100644 index 00000000..d6f8656f --- /dev/null +++ b/crates/audit-logger/src/logger/config.rs @@ -0,0 +1,29 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +// Default value function +fn default_batch_size() -> usize { + 10 +} +fn default_queue_size() -> usize { + 10000 +} +fn default_max_retry() -> u32 { + 5 +} +fn default_retry_interval() -> std::time::Duration { + std::time::Duration::from_secs(3) +} diff --git a/crates/audit-logger/src/logger/dispatch.rs b/crates/audit-logger/src/logger/dispatch.rs new file mode 100644 index 00000000..439d7a7c --- /dev/null +++ b/crates/audit-logger/src/logger/dispatch.rs @@ -0,0 +1,13 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/crates/audit-logger/src/logger/entry.rs b/crates/audit-logger/src/logger/entry.rs new file mode 100644 index 00000000..4d2d25a3 --- /dev/null +++ b/crates/audit-logger/src/logger/entry.rs @@ -0,0 +1,108 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use chrono::{DateTime, Utc}; +use serde::Serialize; +use std::collections::HashMap; +use uuid::Uuid; + +///A Trait for a log entry that can be serialized and sent +pub trait Loggable: Serialize + Send + Sync + 'static { + fn to_json(&self) -> Result { + serde_json::to_string(self) + } +} + +/// Standard log entries +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct LogEntry { + pub deployment_id: String, + pub level: String, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub trace: Option, + pub time: DateTime, + pub request_id: String, +} + +impl Loggable for LogEntry {} + +/// Audit log entry +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AuditEntry { + pub version: String, + pub deployment_id: String, + pub time: DateTime, + pub trigger: String, + pub api: ApiDetails, + pub remote_host: String, + pub request_id: String, + pub user_agent: String, + pub access_key: String, + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub tags: HashMap, +} + +impl Loggable for AuditEntry {} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Trace { + pub message: String, + pub source: Vec, + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub variables: HashMap, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ApiDetails { + pub name: String, + pub bucket: String, + pub object: String, + pub status: String, + pub status_code: u16, + pub time_to_first_byte: String, + pub time_to_response: String, +} + +// Helper functions to create entries +impl AuditEntry { + pub fn new(api_name: &str, bucket: &str, object: &str) -> Self { + AuditEntry { + version: "1".to_string(), + deployment_id: "global-deployment-id".to_string(), + time: Utc::now(), + trigger: "incoming".to_string(), + api: ApiDetails { + name: api_name.to_string(), + bucket: bucket.to_string(), + object: object.to_string(), + status: "OK".to_string(), + status_code: 200, + time_to_first_byte: "10ms".to_string(), + time_to_response: "50ms".to_string(), + }, + remote_host: "127.0.0.1".to_string(), + request_id: Uuid::new_v4().to_string(), + user_agent: "Rust-Client/1.0".to_string(), + access_key: "minioadmin".to_string(), + tags: HashMap::new(), + } + } +} diff --git a/crates/audit-logger/src/logger/factory.rs b/crates/audit-logger/src/logger/factory.rs new file mode 100644 index 00000000..439d7a7c --- /dev/null +++ b/crates/audit-logger/src/logger/factory.rs @@ -0,0 +1,13 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/crates/audit-logger/src/logger/mod.rs b/crates/audit-logger/src/logger/mod.rs new file mode 100644 index 00000000..a192fbcd --- /dev/null +++ b/crates/audit-logger/src/logger/mod.rs @@ -0,0 +1,36 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +pub mod config; +pub mod dispatch; +pub mod entry; +pub mod factory; + +use async_trait::async_trait; +use std::error::Error; + +/// General Log Target Trait +#[async_trait] +pub trait Target: Send + Sync { + /// Send a single logizable entry + async fn send(&self, entry: Box) -> Result<(), Box>; + + /// Returns the unique name of the target + fn name(&self) -> &str; + + /// Close target gracefully, ensuring all buffered logs are processed + async fn shutdown(&self); +} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index c9095b31..7cf5ef86 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -31,8 +31,9 @@ const-str = { workspace = true, optional = true } workspace = true [features] -default = [] +default = ["constants"] +audit = ["dep:const-str", "constants"] constants = ["dep:const-str"] -notify = ["dep:const-str"] -observability = [] +notify = ["dep:const-str", "constants"] +observability = ["constants"] diff --git a/crates/config/src/audit/mod.rs b/crates/config/src/audit/mod.rs new file mode 100644 index 00000000..19ebbdde --- /dev/null +++ b/crates/config/src/audit/mod.rs @@ -0,0 +1,31 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Audit configuration module +//! //! This module defines the configuration for audit systems, including +//! webhook and other audit-related settings. +pub const AUDIT_WEBHOOK_SUB_SYS: &str = "audit_webhook"; + +pub const AUDIT_STORE_EXTENSION: &str = ".audit"; + +pub const WEBHOOK_ENDPOINT: &str = "endpoint"; +pub const WEBHOOK_AUTH_TOKEN: &str = "auth_token"; +pub const WEBHOOK_CLIENT_CERT: &str = "client_cert"; +pub const WEBHOOK_CLIENT_KEY: &str = "client_key"; +pub const WEBHOOK_BATCH_SIZE: &str = "batch_size"; +pub const WEBHOOK_QUEUE_SIZE: &str = "queue_size"; +pub const WEBHOOK_QUEUE_DIR: &str = "queue_dir"; +pub const WEBHOOK_MAX_RETRY: &str = "max_retry"; +pub const WEBHOOK_RETRY_INTERVAL: &str = "retry_interval"; +pub const WEBHOOK_HTTP_TIMEOUT: &str = "http_timeout"; diff --git a/crates/config/src/constants/env.rs b/crates/config/src/constants/env.rs index 22eae738..e78c2b90 100644 --- a/crates/config/src/constants/env.rs +++ b/crates/config/src/constants/env.rs @@ -16,6 +16,13 @@ pub const DEFAULT_DELIMITER: &str = "_"; pub const ENV_PREFIX: &str = "RUSTFS_"; pub const ENV_WORD_DELIMITER: &str = "_"; +pub const DEFAULT_DIR: &str = "/opt/rustfs/events"; // Default directory for event store +pub const DEFAULT_LIMIT: u64 = 100000; // Default store limit + +/// Standard config keys and values. +pub const ENABLE_KEY: &str = "enable"; +pub const COMMENT_KEY: &str = "comment"; + /// Medium-drawn lines separator /// This is used to separate words in environment variable names. pub const ENV_WORD_DELIMITER_DASH: &str = "-"; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 1c2afecd..98bd42c1 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -20,6 +20,8 @@ pub use constants::app::*; pub use constants::env::*; #[cfg(feature = "constants")] pub use constants::tls::*; +#[cfg(feature = "audit")] +pub mod audit; #[cfg(feature = "notify")] pub mod notify; #[cfg(feature = "observability")] diff --git a/crates/config/src/notify/mod.rs b/crates/config/src/notify/mod.rs index 09d8f6f6..ec86985d 100644 --- a/crates/config/src/notify/mod.rs +++ b/crates/config/src/notify/mod.rs @@ -29,14 +29,6 @@ pub const NOTIFY_PREFIX: &str = "notify"; pub const NOTIFY_ROUTE_PREFIX: &str = const_str::concat!(NOTIFY_PREFIX, "_"); -/// Standard config keys and values. -pub const ENABLE_KEY: &str = "enable"; -pub const COMMENT_KEY: &str = "comment"; - -/// Enable values -pub const ENABLE_ON: &str = "on"; -pub const ENABLE_OFF: &str = "off"; - #[allow(dead_code)] pub const NOTIFY_SUB_SYSTEMS: &[&str] = &[NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS]; diff --git a/crates/config/src/notify/mqtt.rs b/crates/config/src/notify/mqtt.rs index ba5ed6b1..0282d82e 100644 --- a/crates/config/src/notify/mqtt.rs +++ b/crates/config/src/notify/mqtt.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::notify::{COMMENT_KEY, ENABLE_KEY}; +use crate::{COMMENT_KEY, ENABLE_KEY}; // MQTT Keys pub const MQTT_BROKER: &str = "broker"; diff --git a/crates/config/src/notify/store.rs b/crates/config/src/notify/store.rs index 5e3dd51c..ed838b05 100644 --- a/crates/config/src/notify/store.rs +++ b/crates/config/src/notify/store.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub const DEFAULT_DIR: &str = "/opt/rustfs/events"; // Default directory for event store -pub const DEFAULT_LIMIT: u64 = 100000; // Default store limit pub const DEFAULT_EXT: &str = ".unknown"; // Default file extension pub const COMPRESS_EXT: &str = ".snappy"; // Extension for compressed files diff --git a/crates/config/src/notify/webhook.rs b/crates/config/src/notify/webhook.rs index b4fefb5f..bbb78716 100644 --- a/crates/config/src/notify/webhook.rs +++ b/crates/config/src/notify/webhook.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::notify::{COMMENT_KEY, ENABLE_KEY}; +use crate::{COMMENT_KEY, ENABLE_KEY}; // Webhook Keys pub const WEBHOOK_ENDPOINT: &str = "endpoint"; diff --git a/crates/ecstore/Cargo.toml b/crates/ecstore/Cargo.toml index 90ea21ab..37559a18 100644 --- a/crates/ecstore/Cargo.toml +++ b/crates/ecstore/Cargo.toml @@ -34,7 +34,7 @@ workspace = true default = [] [dependencies] -rustfs-config = { workspace = true, features = ["constants", "notify"] } +rustfs-config = { workspace = true, features = ["constants", "notify", "audit"] } async-trait.workspace = true bytes.workspace = true byteorder = { workspace = true } diff --git a/crates/ecstore/src/config/audit.rs b/crates/ecstore/src/config/audit.rs new file mode 100644 index 00000000..dde1cbaa --- /dev/null +++ b/crates/ecstore/src/config/audit.rs @@ -0,0 +1,84 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::{KV, KVS}; +use rustfs_config::audit::{ + WEBHOOK_AUTH_TOKEN, WEBHOOK_BATCH_SIZE, WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_HTTP_TIMEOUT, + WEBHOOK_MAX_RETRY, WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_SIZE, WEBHOOK_RETRY_INTERVAL, +}; +use rustfs_config::{DEFAULT_DIR, DEFAULT_LIMIT, ENABLE_KEY, EnableState}; +use std::sync::LazyLock; + +#[allow(dead_code)] +#[allow(clippy::declare_interior_mutable_const)] +/// Default KVS for audit webhook settings. +pub const DEFAULT_AUDIT_WEBHOOK_KVS: LazyLock = LazyLock::new(|| { + KVS(vec![ + KV { + key: ENABLE_KEY.to_owned(), + value: EnableState::Off.to_string(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_ENDPOINT.to_owned(), + value: "".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_AUTH_TOKEN.to_owned(), + value: "".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_CLIENT_CERT.to_owned(), + value: "".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_CLIENT_KEY.to_owned(), + value: "".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_BATCH_SIZE.to_owned(), + value: "1".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_QUEUE_SIZE.to_owned(), + value: DEFAULT_LIMIT.to_string(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_QUEUE_DIR.to_owned(), + value: DEFAULT_DIR.to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_MAX_RETRY.to_owned(), + value: "0".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_RETRY_INTERVAL.to_owned(), + value: "3s".to_owned(), + hidden_if_empty: false, + }, + KV { + key: WEBHOOK_HTTP_TIMEOUT.to_owned(), + value: "5s".to_owned(), + hidden_if_empty: false, + }, + ]) +}); diff --git a/crates/ecstore/src/config/mod.rs b/crates/ecstore/src/config/mod.rs index e957b7d8..f4562b38 100644 --- a/crates/ecstore/src/config/mod.rs +++ b/crates/ecstore/src/config/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod audit; pub mod com; #[allow(dead_code)] pub mod heal; @@ -21,8 +22,9 @@ pub mod storageclass; use crate::error::Result; use crate::store::ECStore; use com::{STORAGE_CLASS_SUB_SYS, lookup_configs, read_config_without_migrate}; +use rustfs_config::COMMENT_KEY; use rustfs_config::DEFAULT_DELIMITER; -use rustfs_config::notify::{COMMENT_KEY, NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS}; +use rustfs_config::notify::{NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::LazyLock; diff --git a/crates/ecstore/src/config/notify.rs b/crates/ecstore/src/config/notify.rs index 2e1dedc2..d9c7b3d8 100644 --- a/crates/ecstore/src/config/notify.rs +++ b/crates/ecstore/src/config/notify.rs @@ -14,10 +14,11 @@ use crate::config::{KV, KVS}; use rustfs_config::notify::{ - COMMENT_KEY, DEFAULT_DIR, DEFAULT_LIMIT, ENABLE_KEY, ENABLE_OFF, MQTT_BROKER, MQTT_KEEP_ALIVE_INTERVAL, MQTT_PASSWORD, - MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_RECONNECT_INTERVAL, MQTT_TOPIC, MQTT_USERNAME, WEBHOOK_AUTH_TOKEN, - WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, + MQTT_BROKER, MQTT_KEEP_ALIVE_INTERVAL, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_RECONNECT_INTERVAL, + MQTT_TOPIC, MQTT_USERNAME, WEBHOOK_AUTH_TOKEN, WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_QUEUE_DIR, + WEBHOOK_QUEUE_LIMIT, }; +use rustfs_config::{COMMENT_KEY, DEFAULT_DIR, DEFAULT_LIMIT, ENABLE_KEY, EnableState}; use std::sync::LazyLock; /// The default configuration collection of webhooks, @@ -26,7 +27,7 @@ pub static DEFAULT_WEBHOOK_KVS: LazyLock = LazyLock::new(|| { KVS(vec![ KV { key: ENABLE_KEY.to_owned(), - value: ENABLE_OFF.to_owned(), + value: EnableState::Off.to_string(), hidden_if_empty: false, }, KV { @@ -73,7 +74,7 @@ pub static DEFAULT_MQTT_KVS: LazyLock = LazyLock::new(|| { KVS(vec![ KV { key: ENABLE_KEY.to_owned(), - value: ENABLE_OFF.to_owned(), + value: EnableState::Off.to_string(), hidden_if_empty: false, }, KV { diff --git a/crates/notify/Cargo.toml b/crates/notify/Cargo.toml index c98bd9ce..541ac861 100644 --- a/crates/notify/Cargo.toml +++ b/crates/notify/Cargo.toml @@ -29,6 +29,7 @@ documentation = "https://docs.rs/rustfs-notify/latest/rustfs_notify/" rustfs-config = { workspace = true, features = ["notify", "constants"] } rustfs-ecstore = { workspace = true } rustfs-utils = { workspace = true, features = ["path", "sys"] } +rustfs-targets = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true, features = ["serde"] } dashmap = { workspace = true } @@ -36,23 +37,18 @@ futures = { workspace = true } form_urlencoded = { workspace = true } once_cell = { workspace = true } quick-xml = { workspace = true, features = ["serialize", "async-tokio"] } -reqwest = { workspace = true } rumqttc = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -snap = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "sync", "time"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } -uuid = { workspace = true, features = ["v4", "serde"] } url = { workspace = true } -urlencoding = { workspace = true } wildmatch = { workspace = true, features = ["serde"] } [dev-dependencies] tokio = { workspace = true, features = ["test-util"] } -reqwest = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } axum = { workspace = true } [lints] diff --git a/crates/notify/examples/base.rs b/crates/notify/examples/base.rs new file mode 100644 index 00000000..3ec51aaf --- /dev/null +++ b/crates/notify/examples/base.rs @@ -0,0 +1,59 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io::IsTerminal; +use tracing_subscriber::{EnvFilter, fmt, prelude::*, util::SubscriberInitExt}; + +#[allow(dead_code)] +fn main() { + init_logger(LogLevel::Info); + tracing::info!("Tracing logger initialized with Info level"); +} + +/// Initialize the tracing log system +pub fn init_logger(level: LogLevel) { + let filter = EnvFilter::default().add_directive(level.into()); + tracing_subscriber::registry() + .with(filter) + .with( + fmt::layer() + .with_target(true) + .with_target(true) + .with_ansi(std::io::stdout().is_terminal()) + .with_thread_names(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true), + ) + .init(); +} + +/// Log level definition +pub enum LogLevel { + Debug, + Info, + Warn, + Error, +} + +impl From for tracing_subscriber::filter::Directive { + fn from(level: LogLevel) -> Self { + match level { + LogLevel::Debug => "debug".parse().unwrap(), + LogLevel::Info => "info".parse().unwrap(), + LogLevel::Warn => "warn".parse().unwrap(), + LogLevel::Error => "error".parse().unwrap(), + } + } +} diff --git a/crates/notify/examples/full_demo.rs b/crates/notify/examples/full_demo.rs index 4eb93df9..458e9ef4 100644 --- a/crates/notify/examples/full_demo.rs +++ b/crates/notify/examples/full_demo.rs @@ -12,15 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod base; + +use base::{LogLevel, init_logger}; +use rustfs_config::EnableState::On; use rustfs_config::notify::{ - DEFAULT_LIMIT, DEFAULT_TARGET, ENABLE_KEY, ENABLE_ON, MQTT_BROKER, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, - MQTT_TOPIC, MQTT_USERNAME, NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS, WEBHOOK_AUTH_TOKEN, WEBHOOK_ENDPOINT, - WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, + DEFAULT_TARGET, MQTT_BROKER, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_TOPIC, MQTT_USERNAME, + NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS, WEBHOOK_AUTH_TOKEN, WEBHOOK_ENDPOINT, WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, }; +use rustfs_config::{DEFAULT_LIMIT, ENABLE_KEY}; use rustfs_ecstore::config::{Config, KV, KVS}; -use rustfs_notify::arn::TargetID; -use rustfs_notify::{BucketNotificationConfig, Event, EventName, LogLevel, NotificationError, init_logger}; +use rustfs_notify::{BucketNotificationConfig, Event, NotificationError}; use rustfs_notify::{initialize, notification_system}; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; use std::sync::Arc; use std::time::Duration; use tracing::info; @@ -46,7 +51,7 @@ async fn main() -> Result<(), NotificationError> { let webhook_kvs_vec = vec![ KV { key: ENABLE_KEY.to_string(), - value: ENABLE_ON.to_string(), + value: On.to_string(), hidden_if_empty: false, }, KV { @@ -85,7 +90,7 @@ async fn main() -> Result<(), NotificationError> { let mqtt_kvs_vec = vec![ KV { key: ENABLE_KEY.to_string(), - value: ENABLE_ON.to_string(), + value: On.to_string(), hidden_if_empty: false, }, KV { @@ -136,6 +141,10 @@ async fn main() -> Result<(), NotificationError> { // Load the configuration and initialize the system *system.config.write().await = config; + info!("---> Initializing notification system with Webhook and MQTT targets..."); + info!("Webhook Endpoint: {}", WEBHOOK_ENDPOINT); + info!("MQTT Broker: {}", MQTT_BROKER); + info!("system.init config: {:?}", system.config.read().await); system.init().await?; info!("✅ System initialized with Webhook and MQTT targets."); diff --git a/crates/notify/examples/full_demo_one.rs b/crates/notify/examples/full_demo_one.rs index 5d06eef9..20b77124 100644 --- a/crates/notify/examples/full_demo_one.rs +++ b/crates/notify/examples/full_demo_one.rs @@ -12,16 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Using Global Accessories +mod base; + +use base::{LogLevel, init_logger}; +use rustfs_config::EnableState::On; use rustfs_config::notify::{ - DEFAULT_LIMIT, DEFAULT_TARGET, ENABLE_KEY, ENABLE_ON, MQTT_BROKER, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, - MQTT_TOPIC, MQTT_USERNAME, NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS, WEBHOOK_AUTH_TOKEN, WEBHOOK_ENDPOINT, - WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, + DEFAULT_TARGET, MQTT_BROKER, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_TOPIC, MQTT_USERNAME, + NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS, WEBHOOK_AUTH_TOKEN, WEBHOOK_ENDPOINT, WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, }; +use rustfs_config::{DEFAULT_LIMIT, ENABLE_KEY}; use rustfs_ecstore::config::{Config, KV, KVS}; -use rustfs_notify::arn::TargetID; -use rustfs_notify::{BucketNotificationConfig, Event, EventName, LogLevel, NotificationError, init_logger}; +use rustfs_notify::{BucketNotificationConfig, Event, NotificationError}; use rustfs_notify::{initialize, notification_system}; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; use std::sync::Arc; use std::time::Duration; use tracing::info; @@ -47,7 +51,7 @@ async fn main() -> Result<(), NotificationError> { let webhook_kvs_vec = vec![ KV { key: ENABLE_KEY.to_string(), - value: ENABLE_ON.to_string(), + value: On.to_string(), hidden_if_empty: false, }, KV { @@ -95,7 +99,7 @@ async fn main() -> Result<(), NotificationError> { let mqtt_kvs_vec = vec![ KV { key: ENABLE_KEY.to_string(), - value: ENABLE_ON.to_string(), + value: On.to_string(), hidden_if_empty: false, }, KV { diff --git a/crates/notify/src/error.rs b/crates/notify/src/error.rs index 8f5fcf0f..b06c6451 100644 --- a/crates/notify/src/error.rs +++ b/crates/notify/src/error.rs @@ -1,98 +1,22 @@ -// Copyright 2024 RustFS Team +// Copyright 2024 RustFS Team // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -use crate::arn::TargetID; +use rustfs_targets::TargetError; +use rustfs_targets::arn::TargetID; use std::io; use thiserror::Error; -/// Error types for the store -#[derive(Debug, Error)] -pub enum StoreError { - #[error("I/O error: {0}")] - Io(#[from] io::Error), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Deserialization error: {0}")] - Deserialization(String), - - #[error("Compression error: {0}")] - Compression(String), - - #[error("Entry limit exceeded")] - LimitExceeded, - - #[error("Entry not found")] - NotFound, - - #[error("Invalid entry: {0}")] - Internal(String), // Added internal error type -} - -/// Error types for targets -#[derive(Debug, Error)] -pub enum TargetError { - #[error("Storage error: {0}")] - Storage(String), - - #[error("Network error: {0}")] - Network(String), - - #[error("Request error: {0}")] - Request(String), - - #[error("Timeout error: {0}")] - Timeout(String), - - #[error("Authentication error: {0}")] - Authentication(String), - - #[error("Configuration error: {0}")] - Configuration(String), - - #[error("Encoding error: {0}")] - Encoding(String), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Target not connected")] - NotConnected, - - #[error("Target initialization failed: {0}")] - Initialization(String), - - #[error("Invalid ARN: {0}")] - InvalidARN(String), - - #[error("Unknown error: {0}")] - Unknown(String), - - #[error("Target is disabled")] - Disabled, - - #[error("Configuration parsing error: {0}")] - ParseError(String), - - #[error("Failed to save configuration: {0}")] - SaveConfig(String), - - #[error("Server not initialized: {0}")] - ServerNotInitialized(String), -} - /// Error types for the notification system #[derive(Debug, Error)] pub enum NotificationError { @@ -135,9 +59,3 @@ pub enum NotificationError { #[error("Server not initialized")] ServerNotInitialized, } - -impl From for TargetError { - fn from(err: url::ParseError) -> Self { - TargetError::Configuration(format!("URL parse error: {err}")) - } -} diff --git a/crates/notify/src/event.rs b/crates/notify/src/event.rs index 308de9d9..6bbda17c 100644 --- a/crates/notify/src/event.rs +++ b/crates/notify/src/event.rs @@ -13,285 +13,11 @@ // limitations under the License. use chrono::{DateTime, Utc}; +use rustfs_targets::EventName; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::fmt; use url::form_urlencoded; -/// Error returned when parsing event name string fails。 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseEventNameError(String); - -impl fmt::Display for ParseEventNameError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Invalid event name:{}", self.0) - } -} - -impl std::error::Error for ParseEventNameError {} - -/// Represents the type of event that occurs on the object. -/// Based on AWS S3 event type and includes RustFS extension. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum EventName { - // Single event type (values are 1-32 for compatible mask logic) - ObjectAccessedGet = 1, - ObjectAccessedGetRetention = 2, - ObjectAccessedGetLegalHold = 3, - ObjectAccessedHead = 4, - ObjectAccessedAttributes = 5, - ObjectCreatedCompleteMultipartUpload = 6, - ObjectCreatedCopy = 7, - ObjectCreatedPost = 8, - ObjectCreatedPut = 9, - ObjectCreatedPutRetention = 10, - ObjectCreatedPutLegalHold = 11, - ObjectCreatedPutTagging = 12, - ObjectCreatedDeleteTagging = 13, - ObjectRemovedDelete = 14, - ObjectRemovedDeleteMarkerCreated = 15, - ObjectRemovedDeleteAllVersions = 16, - ObjectRemovedNoOP = 17, - BucketCreated = 18, - BucketRemoved = 19, - ObjectReplicationFailed = 20, - ObjectReplicationComplete = 21, - ObjectReplicationMissedThreshold = 22, - ObjectReplicationReplicatedAfterThreshold = 23, - ObjectReplicationNotTracked = 24, - ObjectRestorePost = 25, - ObjectRestoreCompleted = 26, - ObjectTransitionFailed = 27, - ObjectTransitionComplete = 28, - ScannerManyVersions = 29, // ObjectManyVersions corresponding to Go - ScannerLargeVersions = 30, // ObjectLargeVersions corresponding to Go - ScannerBigPrefix = 31, // PrefixManyFolders corresponding to Go - LifecycleDelMarkerExpirationDelete = 32, // ILMDelMarkerExpirationDelete corresponding to Go - - // Compound "All" event type (no sequential value for mask) - ObjectAccessedAll, - ObjectCreatedAll, - ObjectRemovedAll, - ObjectReplicationAll, - ObjectRestoreAll, - ObjectTransitionAll, - ObjectScannerAll, // New, from Go - Everything, // New, from Go -} - -// Single event type sequential array for Everything.expand() -const SINGLE_EVENT_NAMES_IN_ORDER: [EventName; 32] = [ - EventName::ObjectAccessedGet, - EventName::ObjectAccessedGetRetention, - EventName::ObjectAccessedGetLegalHold, - EventName::ObjectAccessedHead, - EventName::ObjectAccessedAttributes, - EventName::ObjectCreatedCompleteMultipartUpload, - EventName::ObjectCreatedCopy, - EventName::ObjectCreatedPost, - EventName::ObjectCreatedPut, - EventName::ObjectCreatedPutRetention, - EventName::ObjectCreatedPutLegalHold, - EventName::ObjectCreatedPutTagging, - EventName::ObjectCreatedDeleteTagging, - EventName::ObjectRemovedDelete, - EventName::ObjectRemovedDeleteMarkerCreated, - EventName::ObjectRemovedDeleteAllVersions, - EventName::ObjectRemovedNoOP, - EventName::BucketCreated, - EventName::BucketRemoved, - EventName::ObjectReplicationFailed, - EventName::ObjectReplicationComplete, - EventName::ObjectReplicationMissedThreshold, - EventName::ObjectReplicationReplicatedAfterThreshold, - EventName::ObjectReplicationNotTracked, - EventName::ObjectRestorePost, - EventName::ObjectRestoreCompleted, - EventName::ObjectTransitionFailed, - EventName::ObjectTransitionComplete, - EventName::ScannerManyVersions, - EventName::ScannerLargeVersions, - EventName::ScannerBigPrefix, - EventName::LifecycleDelMarkerExpirationDelete, -]; - -const LAST_SINGLE_TYPE_VALUE: u32 = EventName::LifecycleDelMarkerExpirationDelete as u32; - -impl EventName { - /// The parsed string is EventName. - pub fn parse(s: &str) -> Result { - match s { - "s3:BucketCreated:*" => Ok(EventName::BucketCreated), - "s3:BucketRemoved:*" => Ok(EventName::BucketRemoved), - "s3:ObjectAccessed:*" => Ok(EventName::ObjectAccessedAll), - "s3:ObjectAccessed:Get" => Ok(EventName::ObjectAccessedGet), - "s3:ObjectAccessed:GetRetention" => Ok(EventName::ObjectAccessedGetRetention), - "s3:ObjectAccessed:GetLegalHold" => Ok(EventName::ObjectAccessedGetLegalHold), - "s3:ObjectAccessed:Head" => Ok(EventName::ObjectAccessedHead), - "s3:ObjectAccessed:Attributes" => Ok(EventName::ObjectAccessedAttributes), - "s3:ObjectCreated:*" => Ok(EventName::ObjectCreatedAll), - "s3:ObjectCreated:CompleteMultipartUpload" => Ok(EventName::ObjectCreatedCompleteMultipartUpload), - "s3:ObjectCreated:Copy" => Ok(EventName::ObjectCreatedCopy), - "s3:ObjectCreated:Post" => Ok(EventName::ObjectCreatedPost), - "s3:ObjectCreated:Put" => Ok(EventName::ObjectCreatedPut), - "s3:ObjectCreated:PutRetention" => Ok(EventName::ObjectCreatedPutRetention), - "s3:ObjectCreated:PutLegalHold" => Ok(EventName::ObjectCreatedPutLegalHold), - "s3:ObjectCreated:PutTagging" => Ok(EventName::ObjectCreatedPutTagging), - "s3:ObjectCreated:DeleteTagging" => Ok(EventName::ObjectCreatedDeleteTagging), - "s3:ObjectRemoved:*" => Ok(EventName::ObjectRemovedAll), - "s3:ObjectRemoved:Delete" => Ok(EventName::ObjectRemovedDelete), - "s3:ObjectRemoved:DeleteMarkerCreated" => Ok(EventName::ObjectRemovedDeleteMarkerCreated), - "s3:ObjectRemoved:NoOP" => Ok(EventName::ObjectRemovedNoOP), - "s3:ObjectRemoved:DeleteAllVersions" => Ok(EventName::ObjectRemovedDeleteAllVersions), - "s3:LifecycleDelMarkerExpiration:Delete" => Ok(EventName::LifecycleDelMarkerExpirationDelete), - "s3:Replication:*" => Ok(EventName::ObjectReplicationAll), - "s3:Replication:OperationFailedReplication" => Ok(EventName::ObjectReplicationFailed), - "s3:Replication:OperationCompletedReplication" => Ok(EventName::ObjectReplicationComplete), - "s3:Replication:OperationMissedThreshold" => Ok(EventName::ObjectReplicationMissedThreshold), - "s3:Replication:OperationReplicatedAfterThreshold" => Ok(EventName::ObjectReplicationReplicatedAfterThreshold), - "s3:Replication:OperationNotTracked" => Ok(EventName::ObjectReplicationNotTracked), - "s3:ObjectRestore:*" => Ok(EventName::ObjectRestoreAll), - "s3:ObjectRestore:Post" => Ok(EventName::ObjectRestorePost), - "s3:ObjectRestore:Completed" => Ok(EventName::ObjectRestoreCompleted), - "s3:ObjectTransition:Failed" => Ok(EventName::ObjectTransitionFailed), - "s3:ObjectTransition:Complete" => Ok(EventName::ObjectTransitionComplete), - "s3:ObjectTransition:*" => Ok(EventName::ObjectTransitionAll), - "s3:Scanner:ManyVersions" => Ok(EventName::ScannerManyVersions), - "s3:Scanner:LargeVersions" => Ok(EventName::ScannerLargeVersions), - "s3:Scanner:BigPrefix" => Ok(EventName::ScannerBigPrefix), - // ObjectScannerAll and Everything cannot be parsed from strings, because the Go version also does not define their string representation. - _ => Err(ParseEventNameError(s.to_string())), - } - } - - /// Returns a string representation of the event type. - pub fn as_str(&self) -> &'static str { - match self { - EventName::BucketCreated => "s3:BucketCreated:*", - EventName::BucketRemoved => "s3:BucketRemoved:*", - EventName::ObjectAccessedAll => "s3:ObjectAccessed:*", - EventName::ObjectAccessedGet => "s3:ObjectAccessed:Get", - EventName::ObjectAccessedGetRetention => "s3:ObjectAccessed:GetRetention", - EventName::ObjectAccessedGetLegalHold => "s3:ObjectAccessed:GetLegalHold", - EventName::ObjectAccessedHead => "s3:ObjectAccessed:Head", - EventName::ObjectAccessedAttributes => "s3:ObjectAccessed:Attributes", - EventName::ObjectCreatedAll => "s3:ObjectCreated:*", - EventName::ObjectCreatedCompleteMultipartUpload => "s3:ObjectCreated:CompleteMultipartUpload", - EventName::ObjectCreatedCopy => "s3:ObjectCreated:Copy", - EventName::ObjectCreatedPost => "s3:ObjectCreated:Post", - EventName::ObjectCreatedPut => "s3:ObjectCreated:Put", - EventName::ObjectCreatedPutTagging => "s3:ObjectCreated:PutTagging", - EventName::ObjectCreatedDeleteTagging => "s3:ObjectCreated:DeleteTagging", - EventName::ObjectCreatedPutRetention => "s3:ObjectCreated:PutRetention", - EventName::ObjectCreatedPutLegalHold => "s3:ObjectCreated:PutLegalHold", - EventName::ObjectRemovedAll => "s3:ObjectRemoved:*", - EventName::ObjectRemovedDelete => "s3:ObjectRemoved:Delete", - EventName::ObjectRemovedDeleteMarkerCreated => "s3:ObjectRemoved:DeleteMarkerCreated", - EventName::ObjectRemovedNoOP => "s3:ObjectRemoved:NoOP", - EventName::ObjectRemovedDeleteAllVersions => "s3:ObjectRemoved:DeleteAllVersions", - EventName::LifecycleDelMarkerExpirationDelete => "s3:LifecycleDelMarkerExpiration:Delete", - EventName::ObjectReplicationAll => "s3:Replication:*", - EventName::ObjectReplicationFailed => "s3:Replication:OperationFailedReplication", - EventName::ObjectReplicationComplete => "s3:Replication:OperationCompletedReplication", - EventName::ObjectReplicationNotTracked => "s3:Replication:OperationNotTracked", - EventName::ObjectReplicationMissedThreshold => "s3:Replication:OperationMissedThreshold", - EventName::ObjectReplicationReplicatedAfterThreshold => "s3:Replication:OperationReplicatedAfterThreshold", - EventName::ObjectRestoreAll => "s3:ObjectRestore:*", - EventName::ObjectRestorePost => "s3:ObjectRestore:Post", - EventName::ObjectRestoreCompleted => "s3:ObjectRestore:Completed", - EventName::ObjectTransitionAll => "s3:ObjectTransition:*", - EventName::ObjectTransitionFailed => "s3:ObjectTransition:Failed", - EventName::ObjectTransitionComplete => "s3:ObjectTransition:Complete", - EventName::ScannerManyVersions => "s3:Scanner:ManyVersions", - EventName::ScannerLargeVersions => "s3:Scanner:LargeVersions", - EventName::ScannerBigPrefix => "s3:Scanner:BigPrefix", - // Go's String() returns "" for ObjectScannerAll and Everything - EventName::ObjectScannerAll => "s3:Scanner:*", // Follow the pattern in Go Expand - EventName::Everything => "", // Go String() returns "" to unprocessed - } - } - - /// Returns the extended value of the abbreviation event type. - pub fn expand(&self) -> Vec { - match self { - EventName::ObjectAccessedAll => vec![ - EventName::ObjectAccessedGet, - EventName::ObjectAccessedHead, - EventName::ObjectAccessedGetRetention, - EventName::ObjectAccessedGetLegalHold, - EventName::ObjectAccessedAttributes, - ], - EventName::ObjectCreatedAll => vec![ - EventName::ObjectCreatedCompleteMultipartUpload, - EventName::ObjectCreatedCopy, - EventName::ObjectCreatedPost, - EventName::ObjectCreatedPut, - EventName::ObjectCreatedPutRetention, - EventName::ObjectCreatedPutLegalHold, - EventName::ObjectCreatedPutTagging, - EventName::ObjectCreatedDeleteTagging, - ], - EventName::ObjectRemovedAll => vec![ - EventName::ObjectRemovedDelete, - EventName::ObjectRemovedDeleteMarkerCreated, - EventName::ObjectRemovedNoOP, - EventName::ObjectRemovedDeleteAllVersions, - ], - EventName::ObjectReplicationAll => vec![ - EventName::ObjectReplicationFailed, - EventName::ObjectReplicationComplete, - EventName::ObjectReplicationNotTracked, - EventName::ObjectReplicationMissedThreshold, - EventName::ObjectReplicationReplicatedAfterThreshold, - ], - EventName::ObjectRestoreAll => vec![EventName::ObjectRestorePost, EventName::ObjectRestoreCompleted], - EventName::ObjectTransitionAll => vec![EventName::ObjectTransitionFailed, EventName::ObjectTransitionComplete], - EventName::ObjectScannerAll => vec![ - // New - EventName::ScannerManyVersions, - EventName::ScannerLargeVersions, - EventName::ScannerBigPrefix, - ], - EventName::Everything => { - // New - SINGLE_EVENT_NAMES_IN_ORDER.to_vec() - } - // A single type returns to itself directly - _ => vec![*self], - } - } - - /// Returns the mask of type. - /// The compound "All" type will be expanded. - pub fn mask(&self) -> u64 { - let value = *self as u32; - if value > 0 && value <= LAST_SINGLE_TYPE_VALUE { - // It's a single type - 1u64 << (value - 1) - } else { - // It's a compound type - let mut mask = 0u64; - for n in self.expand() { - mask |= n.mask(); // Recursively call mask - } - mask - } - } -} - -impl fmt::Display for EventName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -/// Convert to `EventName` according to string -impl From<&str> for EventName { - fn from(event_str: &str) -> Self { - EventName::parse(event_str).unwrap_or_else(|e| panic!("{}", e)) - } -} - /// Represents the identity of the user who triggered the event #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Identity { @@ -532,17 +258,6 @@ fn initialize_response_elements(elements: &mut HashMap, keys: &[ } } -/// Represents a log of events for sending to targets -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EventLog { - /// The event name - pub event_name: EventName, - /// The object key - pub key: String, - /// The list of events - pub records: Vec, -} - #[derive(Debug, Clone)] pub struct EventArgs { pub event_name: EventName, diff --git a/crates/notify/src/factory.rs b/crates/notify/src/factory.rs index ada2de48..77945c37 100644 --- a/crates/notify/src/factory.rs +++ b/crates/notify/src/factory.rs @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - error::TargetError, - target::{Target, mqtt::MQTTArgs, webhook::WebhookArgs}, -}; use async_trait::async_trait; use rumqttc::QoS; use rustfs_config::notify::{ - DEFAULT_DIR, DEFAULT_LIMIT, ENV_NOTIFY_MQTT_KEYS, ENV_NOTIFY_WEBHOOK_KEYS, MQTT_BROKER, MQTT_KEEP_ALIVE_INTERVAL, - MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_RECONNECT_INTERVAL, MQTT_TOPIC, MQTT_USERNAME, - NOTIFY_MQTT_KEYS, NOTIFY_WEBHOOK_KEYS, WEBHOOK_AUTH_TOKEN, WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, - WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, + ENV_NOTIFY_MQTT_KEYS, ENV_NOTIFY_WEBHOOK_KEYS, MQTT_BROKER, MQTT_KEEP_ALIVE_INTERVAL, MQTT_PASSWORD, MQTT_QOS, + MQTT_QUEUE_DIR, MQTT_QUEUE_LIMIT, MQTT_RECONNECT_INTERVAL, MQTT_TOPIC, MQTT_USERNAME, NOTIFY_MQTT_KEYS, NOTIFY_WEBHOOK_KEYS, + WEBHOOK_AUTH_TOKEN, WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_QUEUE_DIR, WEBHOOK_QUEUE_LIMIT, }; + +use crate::Event; +use rustfs_config::{DEFAULT_DIR, DEFAULT_LIMIT}; use rustfs_ecstore::config::KVS; +use rustfs_targets::{ + Target, + error::TargetError, + target::{mqtt::MQTTArgs, webhook::WebhookArgs}, +}; use std::collections::HashSet; use std::time::Duration; use tracing::{debug, warn}; @@ -34,7 +37,7 @@ use url::Url; #[async_trait] pub trait TargetFactory: Send + Sync { /// Creates a target from configuration - async fn create_target(&self, id: String, config: &KVS) -> Result, TargetError>; + async fn create_target(&self, id: String, config: &KVS) -> Result + Send + Sync>, TargetError>; /// Validates target configuration fn validate_config(&self, id: &str, config: &KVS) -> Result<(), TargetError>; @@ -53,7 +56,7 @@ pub struct WebhookTargetFactory; #[async_trait] impl TargetFactory for WebhookTargetFactory { - async fn create_target(&self, id: String, config: &KVS) -> Result, TargetError> { + async fn create_target(&self, id: String, config: &KVS) -> Result + Send + Sync>, TargetError> { // All config values are now read directly from the merged `config` KVS. let endpoint = config .lookup(WEBHOOK_ENDPOINT) @@ -72,9 +75,10 @@ impl TargetFactory for WebhookTargetFactory { .unwrap_or(DEFAULT_LIMIT), client_cert: config.lookup(WEBHOOK_CLIENT_CERT).unwrap_or_default(), client_key: config.lookup(WEBHOOK_CLIENT_KEY).unwrap_or_default(), + target_type: rustfs_targets::target::TargetType::NotifyEvent, }; - let target = crate::target::webhook::WebhookTarget::new(id, args)?; + let target = rustfs_targets::target::webhook::WebhookTarget::new(id, args)?; Ok(Box::new(target)) } @@ -119,7 +123,7 @@ pub struct MQTTTargetFactory; #[async_trait] impl TargetFactory for MQTTTargetFactory { - async fn create_target(&self, id: String, config: &KVS) -> Result, TargetError> { + async fn create_target(&self, id: String, config: &KVS) -> Result + Send + Sync>, TargetError> { let broker = config .lookup(MQTT_BROKER) .ok_or_else(|| TargetError::Configuration("Missing MQTT broker".to_string()))?; @@ -161,9 +165,10 @@ impl TargetFactory for MQTTTargetFactory { .lookup(MQTT_QUEUE_LIMIT) .and_then(|v| v.parse::().ok()) .unwrap_or(DEFAULT_LIMIT), + target_type: rustfs_targets::target::TargetType::NotifyEvent, }; - let target = crate::target::mqtt::MQTTTarget::new(id, args)?; + let target = rustfs_targets::target::mqtt::MQTTTarget::new(id, args)?; Ok(Box::new(target)) } diff --git a/crates/notify/src/global.rs b/crates/notify/src/global.rs index b55dd1f3..dd293d1c 100644 --- a/crates/notify/src/global.rs +++ b/crates/notify/src/global.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Event, EventArgs, NotificationError, NotificationSystem}; +use crate::{BucketNotificationConfig, Event, EventArgs, NotificationError, NotificationSystem}; use once_cell::sync::Lazy; use rustfs_ecstore::config::Config; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; use std::sync::{Arc, OnceLock}; -use tracing::instrument; +use tracing::{error, instrument}; static NOTIFICATION_SYSTEM: OnceLock> = OnceLock::new(); // Create a globally unique Notifier instance @@ -57,6 +59,14 @@ pub struct Notifier {} impl Notifier { /// Notify an event asynchronously. /// This is the only entry point for all event notifications in the system. + /// # Parameter + /// - `args`: The event arguments containing details about the event to be notified. + /// + /// # Return value + /// Returns `()`, indicating that the notification has been sent. + /// + /// # Using + /// This function is used to notify events in the system, such as object creation, deletion, or updates. #[instrument(skip(self, args))] pub async fn notify(&self, args: EventArgs) { // Dependency injection or service positioning mode obtain NotificationSystem instance @@ -64,7 +74,7 @@ impl Notifier { // If the notification system itself cannot be retrieved, it will be returned directly Some(sys) => sys, None => { - tracing::error!("Notification system is not initialized."); + error!("Notification system is not initialized."); return; } }; @@ -76,6 +86,7 @@ impl Notifier { // Check if any subscribers are interested in the event if !notification_sys.has_subscriber(&args.bucket_name, &args.event_name).await { + error!("No subscribers for event: {} in bucket: {}", args.event_name, args.bucket_name); return; } @@ -83,4 +94,96 @@ impl Notifier { let event = Arc::new(Event::new(args)); notification_sys.send_event(event).await; } + + /// Add notification rules for the specified bucket and load configuration + /// # Parameter + /// - `bucket_name`: The name of the target bucket. + /// - `region`: The area where bucket is located. + /// - `event_names`: A list of event names that trigger notifications. + /// - `prefix`: The prefix of the object key that triggers notifications. + /// - `suffix`: The suffix of the object key that triggers notifications. + /// - `target_ids`: A list of target IDs that will receive notifications. + /// + /// # Return value + /// Returns `Result<(), NotificationError>`, Ok on success, and an error on failure + /// + /// # Using + /// This function allows you to dynamically add notification rules for a specific bucket. + pub async fn add_bucket_notification_rule( + &self, + bucket_name: &str, + region: &str, + event_names: &[EventName], + prefix: &str, + suffix: &str, + target_ids: &[TargetID], + ) -> Result<(), NotificationError> { + // Construct pattern, simple splicing of prefixes and suffixes + let mut pattern = String::new(); + if !prefix.is_empty() { + pattern.push_str(prefix); + } + pattern.push('*'); + if !suffix.is_empty() { + pattern.push_str(suffix); + } + + // Create BucketNotificationConfig + let mut bucket_config = BucketNotificationConfig::new(region); + for target_id in target_ids { + bucket_config.add_rule(event_names, pattern.clone(), target_id.clone()); + } + + // Get global NotificationSystem + let notification_sys = match notification_system() { + Some(sys) => sys, + None => return Err(NotificationError::ServerNotInitialized), + }; + + // Loading configuration + notification_sys + .load_bucket_notification_config(bucket_name, &bucket_config) + .await + } + + /// Dynamically add notification rules according to different event types. + /// + /// # Parameter + /// - `bucket_name`: The name of the target bucket. + /// - `region`: The area where bucket is located. + /// - `event_rules`: Each rule contains a list of event types, prefixes, suffixes, and target IDs. + /// + /// # Return value + /// Returns `Result<(), NotificationError>`, Ok on success, and an error on failure. + /// + /// # Using + /// Supports notification rules for adding multiple event types, prefixes, suffixes, and targets to the same bucket in batches. + pub async fn add_event_specific_rules( + &self, + bucket_name: &str, + region: &str, + event_rules: &[(Vec, &str, &str, Vec)], + ) -> Result<(), NotificationError> { + let mut bucket_config = BucketNotificationConfig::new(region); + + for (event_names, prefix, suffix, target_ids) in event_rules { + // Use `new_pattern` to construct a matching pattern + let pattern = crate::rules::pattern::new_pattern(Some(prefix), Some(suffix)); + + for target_id in target_ids { + bucket_config.add_rule(event_names, pattern.clone(), target_id.clone()); + } + } + + // Get global NotificationSystem instance + let notification_sys = match notification_system() { + Some(sys) => sys, + None => return Err(NotificationError::ServerNotInitialized), + }; + + // Loading configuration + notification_sys + .load_bucket_notification_config(bucket_name, &bucket_config) + .await + } } diff --git a/crates/notify/src/integration.rs b/crates/notify/src/integration.rs index 7f786285..d03454d2 100644 --- a/crates/notify/src/integration.rs +++ b/crates/notify/src/integration.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::arn::TargetID; -use crate::store::{Key, Store}; use crate::{ - Event, EventName, StoreError, Target, error::NotificationError, notifier::EventNotifier, registry::TargetRegistry, - rules::BucketNotificationConfig, stream, + Event, error::NotificationError, notifier::EventNotifier, registry::TargetRegistry, rules::BucketNotificationConfig, stream, }; use rustfs_ecstore::config::{Config, KVS}; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; +use rustfs_targets::store::{Key, Store}; +use rustfs_targets::target::EntityTarget; +use rustfs_targets::{StoreError, Target}; use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -127,7 +129,7 @@ impl NotificationSystem { let config = self.config.read().await; debug!("Initializing notification system with config: {:?}", *config); - let targets: Vec> = self.registry.create_targets_from_config(&config).await?; + let targets: Vec + Send + Sync>> = self.registry.create_targets_from_config(&config).await?; info!("{} notification targets were created", targets.len()); @@ -318,8 +320,8 @@ impl NotificationSystem { /// Enhanced event stream startup function, including monitoring and concurrency control fn enhanced_start_event_stream( &self, - store: Box + Send>, - target: Arc, + store: Box, Error = StoreError, Key = Key> + Send>, + target: Arc + Send + Sync>, metrics: Arc, semaphore: Arc, ) -> mpsc::Sender<()> { @@ -348,7 +350,7 @@ impl NotificationSystem { // Create a new target from configuration // This function will now be responsible for merging env, creating and persisting the final configuration. - let targets: Vec> = self + let targets: Vec + Send + Sync>> = self .registry .create_targets_from_config(&new_config) .await diff --git a/crates/notify/src/lib.rs b/crates/notify/src/lib.rs index 837020ca..5da8d58a 100644 --- a/crates/notify/src/lib.rs +++ b/crates/notify/src/lib.rs @@ -18,7 +18,6 @@ //! It supports sending events to various targets //! (like Webhook and MQTT) and includes features like event persistence and retry on failure. -pub mod arn; pub mod error; pub mod event; pub mod factory; @@ -27,59 +26,10 @@ pub mod integration; pub mod notifier; pub mod registry; pub mod rules; -pub mod store; pub mod stream; -pub mod target; - // Re-exports -pub use error::{NotificationError, StoreError, TargetError}; -pub use event::{Event, EventArgs, EventLog, EventName}; +pub use error::NotificationError; +pub use event::{Event, EventArgs}; pub use global::{initialize, is_notification_system_initialized, notification_system}; pub use integration::NotificationSystem; pub use rules::BucketNotificationConfig; -use std::io::IsTerminal; -pub use target::Target; - -use tracing_subscriber::{EnvFilter, fmt, prelude::*, util::SubscriberInitExt}; - -/// Initialize the tracing log system -/// -/// # Example -/// ``` -/// rustfs_notify::init_logger(rustfs_notify::LogLevel::Info); -/// ``` -pub fn init_logger(level: LogLevel) { - let filter = EnvFilter::default().add_directive(level.into()); - tracing_subscriber::registry() - .with(filter) - .with( - fmt::layer() - .with_target(true) - .with_target(true) - .with_ansi(std::io::stdout().is_terminal()) - .with_thread_names(true) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true), - ) - .init(); -} - -/// Log level definition -pub enum LogLevel { - Debug, - Info, - Warn, - Error, -} - -impl From for tracing_subscriber::filter::Directive { - fn from(level: LogLevel) -> Self { - match level { - LogLevel::Debug => "debug".parse().unwrap(), - LogLevel::Info => "info".parse().unwrap(), - LogLevel::Warn => "warn".parse().unwrap(), - LogLevel::Error => "error".parse().unwrap(), - } - } -} diff --git a/crates/notify/src/notifier.rs b/crates/notify/src/notifier.rs index 24a765fd..c4586c69 100644 --- a/crates/notify/src/notifier.rs +++ b/crates/notify/src/notifier.rs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::arn::TargetID; -use crate::{EventName, error::NotificationError, event::Event, rules::RulesMap, target::Target}; +use crate::{error::NotificationError, event::Event, rules::RulesMap}; use dashmap::DashMap; +use rustfs_targets::EventName; +use rustfs_targets::Target; +use rustfs_targets::arn::TargetID; +use rustfs_targets::target::EntityTarget; use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLock; use tracing::{debug, error, info, instrument, warn}; @@ -121,7 +124,7 @@ impl EventNotifier { } /// Sends an event to the appropriate targets based on the bucket rules - #[instrument(skip(self, event))] + #[instrument(skip_all)] pub async fn send(&self, event: Arc) { let bucket_name = &event.s3.bucket.name; let object_key = &event.s3.object.key; @@ -149,8 +152,15 @@ impl EventNotifier { let target_name_for_task = cloned_target_for_task.name(); // Get the name before generating the task debug!("Preparing to send event to target: {}", target_name_for_task); // Use cloned data in closures to avoid borrowing conflicts + // Create an EntityTarget from the event + let entity_target: Arc> = Arc::new(EntityTarget { + object_name: object_key.to_string(), + bucket_name: bucket_name.to_string(), + event_name, + data: event_clone.clone().as_ref().clone(), + }); let handle = tokio::spawn(async move { - if let Err(e) = cloned_target_for_task.save(event_clone).await { + if let Err(e) = cloned_target_for_task.save(entity_target.clone()).await { error!("Failed to send event to target {}: {}", target_name_for_task, e); } else { debug!("Successfully saved event to target {}", target_name_for_task); @@ -180,7 +190,7 @@ impl EventNotifier { #[instrument(skip(self, targets_to_init))] pub async fn init_bucket_targets( &self, - targets_to_init: Vec>, + targets_to_init: Vec + Send + Sync>>, ) -> Result<(), NotificationError> { // Currently active, simpler logic let mut target_list_guard = self.target_list.write().await; //Gets a write lock for the TargetList @@ -189,7 +199,7 @@ impl EventNotifier { debug!("init bucket target: {}", target_boxed.name()); // TargetList::add method expectations Arc // Therefore, you need to convert Box to Arc - let target_arc: Arc = Arc::from(target_boxed); + let target_arc: Arc + Send + Sync> = Arc::from(target_boxed); target_list_guard.add(target_arc)?; // Add Arc to the list } info!( @@ -203,7 +213,7 @@ impl EventNotifier { /// A thread-safe list of targets pub struct TargetList { - targets: HashMap>, + targets: HashMap + Send + Sync>>, } impl Default for TargetList { @@ -219,7 +229,7 @@ impl TargetList { } /// Adds a target to the list - pub fn add(&mut self, target: Arc) -> Result<(), NotificationError> { + pub fn add(&mut self, target: Arc + Send + Sync>) -> Result<(), NotificationError> { let id = target.id(); if self.targets.contains_key(&id) { // Potentially update or log a warning/error if replacing an existing target. @@ -231,7 +241,7 @@ impl TargetList { /// Removes a target by ID. Note: This does not stop its associated event stream. /// Stream cancellation should be handled by EventNotifier. - pub async fn remove_target_only(&mut self, id: &TargetID) -> Option> { + pub async fn remove_target_only(&mut self, id: &TargetID) -> Option + Send + Sync>> { if let Some(target_arc) = self.targets.remove(id) { if let Err(e) = target_arc.close().await { // Target's own close logic @@ -258,7 +268,7 @@ impl TargetList { } /// Returns a target by ID - pub fn get(&self, id: &TargetID) -> Option> { + pub fn get(&self, id: &TargetID) -> Option + Send + Sync>> { self.targets.get(id).cloned() } diff --git a/crates/notify/src/registry.rs b/crates/notify/src/registry.rs index a65a9202..8bbaaa89 100644 --- a/crates/notify/src/registry.rs +++ b/crates/notify/src/registry.rs @@ -12,16 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::target::ChannelTargetType; -use crate::{ - error::TargetError, - factory::{MQTTTargetFactory, TargetFactory, WebhookTargetFactory}, - target::Target, -}; +use crate::Event; +use crate::factory::{MQTTTargetFactory, TargetFactory, WebhookTargetFactory}; use futures::stream::{FuturesUnordered, StreamExt}; -use rustfs_config::notify::{ENABLE_KEY, NOTIFY_ROUTE_PREFIX}; -use rustfs_config::{DEFAULT_DELIMITER, ENV_PREFIX}; +use rustfs_config::notify::NOTIFY_ROUTE_PREFIX; +use rustfs_config::{DEFAULT_DELIMITER, ENABLE_KEY, ENV_PREFIX}; use rustfs_ecstore::config::{Config, KVS}; +use rustfs_targets::Target; +use rustfs_targets::TargetError; +use rustfs_targets::target::ChannelTargetType; use std::collections::{HashMap, HashSet}; use tracing::{debug, error, info, warn}; @@ -61,7 +60,7 @@ impl TargetRegistry { target_type: &str, id: String, config: &KVS, - ) -> Result, TargetError> { + ) -> Result + Send + Sync>, TargetError> { let factory = self .factories .get(target_type) @@ -83,7 +82,10 @@ impl TargetRegistry { /// 4. Combine the default configuration, file configuration, and environment variable configuration for each instance. /// 5. If the instance is enabled, create an asynchronous task for it to instantiate. /// 6. Concurrency executes all creation tasks and collects results. - pub async fn create_targets_from_config(&self, config: &Config) -> Result>, TargetError> { + pub async fn create_targets_from_config( + &self, + config: &Config, + ) -> Result + Send + Sync>>, TargetError> { // Collect only environment variables with the relevant prefix to reduce memory usage let all_env: Vec<(String, String)> = std::env::vars().filter(|(key, _)| key.starts_with(ENV_PREFIX)).collect(); // A collection of asynchronous tasks for concurrently executing target creation diff --git a/crates/notify/src/rules/config.rs b/crates/notify/src/rules/config.rs index c9e92953..81fbc4eb 100644 --- a/crates/notify/src/rules/config.rs +++ b/crates/notify/src/rules/config.rs @@ -14,11 +14,11 @@ use super::rules_map::RulesMap; use super::xml_config::ParseConfigError as BucketNotificationConfigError; -use crate::EventName; -use crate::arn::TargetID; use crate::rules::NotificationConfiguration; use crate::rules::pattern_rules; use crate::rules::target_id_set; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::Read; diff --git a/crates/notify/src/rules/pattern_rules.rs b/crates/notify/src/rules/pattern_rules.rs index 01ad800c..ecc3dfaa 100644 --- a/crates/notify/src/rules/pattern_rules.rs +++ b/crates/notify/src/rules/pattern_rules.rs @@ -14,7 +14,7 @@ use super::pattern; use super::target_id_set::TargetIdSet; -use crate::arn::TargetID; +use rustfs_targets::arn::TargetID; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/crates/notify/src/rules/rules_map.rs b/crates/notify/src/rules/rules_map.rs index 9be13843..02aae743 100644 --- a/crates/notify/src/rules/rules_map.rs +++ b/crates/notify/src/rules/rules_map.rs @@ -14,8 +14,8 @@ use super::pattern_rules::PatternRules; use super::target_id_set::TargetIdSet; -use crate::arn::TargetID; -use crate::event::EventName; +use rustfs_targets::EventName; +use rustfs_targets::arn::TargetID; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/crates/notify/src/rules/target_id_set.rs b/crates/notify/src/rules/target_id_set.rs index 129d0dc3..45f790dc 100644 --- a/crates/notify/src/rules/target_id_set.rs +++ b/crates/notify/src/rules/target_id_set.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::arn::TargetID; +use rustfs_targets::arn::TargetID; use std::collections::HashSet; /// TargetIDSet - A collection representation of TargetID. diff --git a/crates/notify/src/rules/xml_config.rs b/crates/notify/src/rules/xml_config.rs index 5925a66e..e8401dfd 100644 --- a/crates/notify/src/rules/xml_config.rs +++ b/crates/notify/src/rules/xml_config.rs @@ -13,8 +13,8 @@ // limitations under the License. use super::pattern; -use crate::arn::{ARN, ArnError, TargetIDError}; -use crate::event::EventName; +use rustfs_targets::EventName; +use rustfs_targets::arn::{ARN, ArnError, TargetIDError}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::io::Read; diff --git a/crates/notify/src/stream.rs b/crates/notify/src/stream.rs index 0d3d5255..9b37c13b 100644 --- a/crates/notify/src/stream.rs +++ b/crates/notify/src/stream.rs @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - Event, StoreError, - error::TargetError, - integration::NotificationMetrics, - store::{Key, Store}, - target::Target, -}; +use crate::{Event, integration::NotificationMetrics}; +use rustfs_targets::StoreError; +use rustfs_targets::Target; +use rustfs_targets::TargetError; +use rustfs_targets::store::{Key, Store}; +use rustfs_targets::target::EntityTarget; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{Semaphore, mpsc}; @@ -28,7 +27,7 @@ use tracing::{debug, error, info, warn}; /// Streams events from the store to the target pub async fn stream_events( store: &mut (dyn Store + Send), - target: &dyn Target, + target: &dyn Target, mut cancel_rx: mpsc::Receiver<()>, ) { info!("Starting event stream for target: {}", target.name()); @@ -107,7 +106,7 @@ pub async fn stream_events( /// Starts the event streaming process for a target pub fn start_event_stream( mut store: Box + Send>, - target: Arc, + target: Arc + Send + Sync>, ) -> mpsc::Sender<()> { let (cancel_tx, cancel_rx) = mpsc::channel(1); @@ -121,8 +120,8 @@ pub fn start_event_stream( /// Start event stream with batch processing pub fn start_event_stream_with_batching( - mut store: Box + Send>, - target: Arc, + mut store: Box, Error = StoreError, Key = Key> + Send>, + target: Arc + Send + Sync>, metrics: Arc, semaphore: Arc, ) -> mpsc::Sender<()> { @@ -138,8 +137,8 @@ pub fn start_event_stream_with_batching( /// Event stream processing with batch processing pub async fn stream_events_with_batching( - store: &mut (dyn Store + Send), - target: &dyn Target, + store: &mut (dyn Store, Error = StoreError, Key = Key> + Send), + target: &dyn Target, mut cancel_rx: mpsc::Receiver<()>, metrics: Arc, semaphore: Arc, @@ -156,7 +155,7 @@ pub async fn stream_events_with_batching( const MAX_RETRIES: usize = 5; const BASE_RETRY_DELAY: Duration = Duration::from_secs(2); - let mut batch = Vec::with_capacity(batch_size); + let mut batch: Vec> = Vec::with_capacity(batch_size); let mut batch_keys = Vec::with_capacity(batch_size); let mut last_flush = Instant::now(); @@ -234,9 +233,9 @@ pub async fn stream_events_with_batching( /// Processing event batches async fn process_batch( - batch: &mut Vec, + batch: &mut Vec>, batch_keys: &mut Vec, - target: &dyn Target, + target: &dyn Target, max_retries: usize, base_delay: Duration, metrics: &Arc, diff --git a/crates/rio/Cargo.toml b/crates/rio/Cargo.toml index 875b5fc2..835f23e2 100644 --- a/crates/rio/Cargo.toml +++ b/crates/rio/Cargo.toml @@ -45,4 +45,4 @@ serde_json.workspace = true md-5 = { workspace = true } [dev-dependencies] -tokio-test = { workspace = true } +tokio-test = { workspace = true } \ No newline at end of file diff --git a/crates/targets/Cargo.toml b/crates/targets/Cargo.toml new file mode 100644 index 00000000..f0ac418b --- /dev/null +++ b/crates/targets/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "rustfs-targets" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true +homepage.workspace = true +description = "Notification target abstraction and implementations for RustFS" +keywords = ["file-system", "notification", "target", "rustfs", "Minio"] +categories = ["web-programming", "development-tools", "filesystem"] +documentation = "https://docs.rs/rustfs-target/latest/rustfs_target/" + +[dependencies] +rustfs-config = { workspace = true, features = ["notify", "constants", "audit"] } +rustfs-utils = { workspace = true, features = ["sys"] } +async-trait = { workspace = true } +reqwest = { workspace = true } +rumqttc = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +snap = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "sync", "time"] } +tracing = { workspace = true } +url = { workspace = true } +urlencoding = { workspace = true } +uuid = { workspace = true, features = ["v4", "serde"] } + +[lints] +workspace = true diff --git a/crates/notify/src/arn.rs b/crates/targets/src/arn.rs similarity index 100% rename from crates/notify/src/arn.rs rename to crates/targets/src/arn.rs diff --git a/crates/targets/src/error.rs b/crates/targets/src/error.rs new file mode 100644 index 00000000..2e6d6091 --- /dev/null +++ b/crates/targets/src/error.rs @@ -0,0 +1,99 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io; +use thiserror::Error; + +/// Error types for the store +#[derive(Debug, Error)] +pub enum StoreError { + #[error("I/O error: {0}")] + Io(#[from] io::Error), + + #[error("Serialization error: {0}")] + Serialization(String), + + #[error("Deserialization error: {0}")] + Deserialization(String), + + #[error("Compression error: {0}")] + Compression(String), + + #[error("Entry limit exceeded")] + LimitExceeded, + + #[error("Entry not found")] + NotFound, + + #[error("Invalid entry: {0}")] + Internal(String), // Added internal error type +} + +/// Error types for targets +#[derive(Debug, Error)] +pub enum TargetError { + #[error("Storage error: {0}")] + Storage(String), + + #[error("Network error: {0}")] + Network(String), + + #[error("Request error: {0}")] + Request(String), + + #[error("Timeout error: {0}")] + Timeout(String), + + #[error("Authentication error: {0}")] + Authentication(String), + + #[error("Configuration error: {0}")] + Configuration(String), + + #[error("Encoding error: {0}")] + Encoding(String), + + #[error("Serialization error: {0}")] + Serialization(String), + + #[error("Target not connected")] + NotConnected, + + #[error("Target initialization failed: {0}")] + Initialization(String), + + #[error("Invalid ARN: {0}")] + InvalidARN(String), + + #[error("Unknown error: {0}")] + Unknown(String), + + #[error("Target is disabled")] + Disabled, + + #[error("Configuration parsing error: {0}")] + ParseError(String), + + #[error("Failed to save configuration: {0}")] + SaveConfig(String), + + #[error("Server not initialized: {0}")] + ServerNotInitialized(String), +} + +impl From for TargetError { + fn from(err: url::ParseError) -> Self { + TargetError::Configuration(format!("URL parse error: {err}")) + } +} diff --git a/crates/targets/src/event_name.rs b/crates/targets/src/event_name.rs new file mode 100644 index 00000000..59c1a851 --- /dev/null +++ b/crates/targets/src/event_name.rs @@ -0,0 +1,290 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Error returned when parsing event name string fails. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseEventNameError(String); + +impl fmt::Display for ParseEventNameError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Invalid event name:{}", self.0) + } +} + +impl std::error::Error for ParseEventNameError {} + +/// Represents the type of event that occurs on the object. +/// Based on AWS S3 event type and includes RustFS extension. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum EventName { + // Single event type (values are 1-32 for compatible mask logic) + ObjectAccessedGet = 1, + ObjectAccessedGetRetention = 2, + ObjectAccessedGetLegalHold = 3, + ObjectAccessedHead = 4, + ObjectAccessedAttributes = 5, + ObjectCreatedCompleteMultipartUpload = 6, + ObjectCreatedCopy = 7, + ObjectCreatedPost = 8, + ObjectCreatedPut = 9, + ObjectCreatedPutRetention = 10, + ObjectCreatedPutLegalHold = 11, + ObjectCreatedPutTagging = 12, + ObjectCreatedDeleteTagging = 13, + ObjectRemovedDelete = 14, + ObjectRemovedDeleteMarkerCreated = 15, + ObjectRemovedDeleteAllVersions = 16, + ObjectRemovedNoOP = 17, + BucketCreated = 18, + BucketRemoved = 19, + ObjectReplicationFailed = 20, + ObjectReplicationComplete = 21, + ObjectReplicationMissedThreshold = 22, + ObjectReplicationReplicatedAfterThreshold = 23, + ObjectReplicationNotTracked = 24, + ObjectRestorePost = 25, + ObjectRestoreCompleted = 26, + ObjectTransitionFailed = 27, + ObjectTransitionComplete = 28, + ScannerManyVersions = 29, // ObjectManyVersions corresponding to Go + ScannerLargeVersions = 30, // ObjectLargeVersions corresponding to Go + ScannerBigPrefix = 31, // PrefixManyFolders corresponding to Go + LifecycleDelMarkerExpirationDelete = 32, // ILMDelMarkerExpirationDelete corresponding to Go + + // Compound "All" event type (no sequential value for mask) + ObjectAccessedAll, + ObjectCreatedAll, + ObjectRemovedAll, + ObjectReplicationAll, + ObjectRestoreAll, + ObjectTransitionAll, + ObjectScannerAll, // New, from Go + Everything, // New, from Go +} + +// Single event type sequential array for Everything.expand() +const SINGLE_EVENT_NAMES_IN_ORDER: [EventName; 32] = [ + EventName::ObjectAccessedGet, + EventName::ObjectAccessedGetRetention, + EventName::ObjectAccessedGetLegalHold, + EventName::ObjectAccessedHead, + EventName::ObjectAccessedAttributes, + EventName::ObjectCreatedCompleteMultipartUpload, + EventName::ObjectCreatedCopy, + EventName::ObjectCreatedPost, + EventName::ObjectCreatedPut, + EventName::ObjectCreatedPutRetention, + EventName::ObjectCreatedPutLegalHold, + EventName::ObjectCreatedPutTagging, + EventName::ObjectCreatedDeleteTagging, + EventName::ObjectRemovedDelete, + EventName::ObjectRemovedDeleteMarkerCreated, + EventName::ObjectRemovedDeleteAllVersions, + EventName::ObjectRemovedNoOP, + EventName::BucketCreated, + EventName::BucketRemoved, + EventName::ObjectReplicationFailed, + EventName::ObjectReplicationComplete, + EventName::ObjectReplicationMissedThreshold, + EventName::ObjectReplicationReplicatedAfterThreshold, + EventName::ObjectReplicationNotTracked, + EventName::ObjectRestorePost, + EventName::ObjectRestoreCompleted, + EventName::ObjectTransitionFailed, + EventName::ObjectTransitionComplete, + EventName::ScannerManyVersions, + EventName::ScannerLargeVersions, + EventName::ScannerBigPrefix, + EventName::LifecycleDelMarkerExpirationDelete, +]; + +const LAST_SINGLE_TYPE_VALUE: u32 = EventName::LifecycleDelMarkerExpirationDelete as u32; + +impl EventName { + /// The parsed string is EventName. + pub fn parse(s: &str) -> Result { + match s { + "s3:BucketCreated:*" => Ok(EventName::BucketCreated), + "s3:BucketRemoved:*" => Ok(EventName::BucketRemoved), + "s3:ObjectAccessed:*" => Ok(EventName::ObjectAccessedAll), + "s3:ObjectAccessed:Get" => Ok(EventName::ObjectAccessedGet), + "s3:ObjectAccessed:GetRetention" => Ok(EventName::ObjectAccessedGetRetention), + "s3:ObjectAccessed:GetLegalHold" => Ok(EventName::ObjectAccessedGetLegalHold), + "s3:ObjectAccessed:Head" => Ok(EventName::ObjectAccessedHead), + "s3:ObjectAccessed:Attributes" => Ok(EventName::ObjectAccessedAttributes), + "s3:ObjectCreated:*" => Ok(EventName::ObjectCreatedAll), + "s3:ObjectCreated:CompleteMultipartUpload" => Ok(EventName::ObjectCreatedCompleteMultipartUpload), + "s3:ObjectCreated:Copy" => Ok(EventName::ObjectCreatedCopy), + "s3:ObjectCreated:Post" => Ok(EventName::ObjectCreatedPost), + "s3:ObjectCreated:Put" => Ok(EventName::ObjectCreatedPut), + "s3:ObjectCreated:PutRetention" => Ok(EventName::ObjectCreatedPutRetention), + "s3:ObjectCreated:PutLegalHold" => Ok(EventName::ObjectCreatedPutLegalHold), + "s3:ObjectCreated:PutTagging" => Ok(EventName::ObjectCreatedPutTagging), + "s3:ObjectCreated:DeleteTagging" => Ok(EventName::ObjectCreatedDeleteTagging), + "s3:ObjectRemoved:*" => Ok(EventName::ObjectRemovedAll), + "s3:ObjectRemoved:Delete" => Ok(EventName::ObjectRemovedDelete), + "s3:ObjectRemoved:DeleteMarkerCreated" => Ok(EventName::ObjectRemovedDeleteMarkerCreated), + "s3:ObjectRemoved:NoOP" => Ok(EventName::ObjectRemovedNoOP), + "s3:ObjectRemoved:DeleteAllVersions" => Ok(EventName::ObjectRemovedDeleteAllVersions), + "s3:LifecycleDelMarkerExpiration:Delete" => Ok(EventName::LifecycleDelMarkerExpirationDelete), + "s3:Replication:*" => Ok(EventName::ObjectReplicationAll), + "s3:Replication:OperationFailedReplication" => Ok(EventName::ObjectReplicationFailed), + "s3:Replication:OperationCompletedReplication" => Ok(EventName::ObjectReplicationComplete), + "s3:Replication:OperationMissedThreshold" => Ok(EventName::ObjectReplicationMissedThreshold), + "s3:Replication:OperationReplicatedAfterThreshold" => Ok(EventName::ObjectReplicationReplicatedAfterThreshold), + "s3:Replication:OperationNotTracked" => Ok(EventName::ObjectReplicationNotTracked), + "s3:ObjectRestore:*" => Ok(EventName::ObjectRestoreAll), + "s3:ObjectRestore:Post" => Ok(EventName::ObjectRestorePost), + "s3:ObjectRestore:Completed" => Ok(EventName::ObjectRestoreCompleted), + "s3:ObjectTransition:Failed" => Ok(EventName::ObjectTransitionFailed), + "s3:ObjectTransition:Complete" => Ok(EventName::ObjectTransitionComplete), + "s3:ObjectTransition:*" => Ok(EventName::ObjectTransitionAll), + "s3:Scanner:ManyVersions" => Ok(EventName::ScannerManyVersions), + "s3:Scanner:LargeVersions" => Ok(EventName::ScannerLargeVersions), + "s3:Scanner:BigPrefix" => Ok(EventName::ScannerBigPrefix), + // ObjectScannerAll and Everything cannot be parsed from strings, because the Go version also does not define their string representation. + _ => Err(ParseEventNameError(s.to_string())), + } + } + + /// Returns a string representation of the event type. + pub fn as_str(&self) -> &'static str { + match self { + EventName::BucketCreated => "s3:BucketCreated:*", + EventName::BucketRemoved => "s3:BucketRemoved:*", + EventName::ObjectAccessedAll => "s3:ObjectAccessed:*", + EventName::ObjectAccessedGet => "s3:ObjectAccessed:Get", + EventName::ObjectAccessedGetRetention => "s3:ObjectAccessed:GetRetention", + EventName::ObjectAccessedGetLegalHold => "s3:ObjectAccessed:GetLegalHold", + EventName::ObjectAccessedHead => "s3:ObjectAccessed:Head", + EventName::ObjectAccessedAttributes => "s3:ObjectAccessed:Attributes", + EventName::ObjectCreatedAll => "s3:ObjectCreated:*", + EventName::ObjectCreatedCompleteMultipartUpload => "s3:ObjectCreated:CompleteMultipartUpload", + EventName::ObjectCreatedCopy => "s3:ObjectCreated:Copy", + EventName::ObjectCreatedPost => "s3:ObjectCreated:Post", + EventName::ObjectCreatedPut => "s3:ObjectCreated:Put", + EventName::ObjectCreatedPutTagging => "s3:ObjectCreated:PutTagging", + EventName::ObjectCreatedDeleteTagging => "s3:ObjectCreated:DeleteTagging", + EventName::ObjectCreatedPutRetention => "s3:ObjectCreated:PutRetention", + EventName::ObjectCreatedPutLegalHold => "s3:ObjectCreated:PutLegalHold", + EventName::ObjectRemovedAll => "s3:ObjectRemoved:*", + EventName::ObjectRemovedDelete => "s3:ObjectRemoved:Delete", + EventName::ObjectRemovedDeleteMarkerCreated => "s3:ObjectRemoved:DeleteMarkerCreated", + EventName::ObjectRemovedNoOP => "s3:ObjectRemoved:NoOP", + EventName::ObjectRemovedDeleteAllVersions => "s3:ObjectRemoved:DeleteAllVersions", + EventName::LifecycleDelMarkerExpirationDelete => "s3:LifecycleDelMarkerExpiration:Delete", + EventName::ObjectReplicationAll => "s3:Replication:*", + EventName::ObjectReplicationFailed => "s3:Replication:OperationFailedReplication", + EventName::ObjectReplicationComplete => "s3:Replication:OperationCompletedReplication", + EventName::ObjectReplicationNotTracked => "s3:Replication:OperationNotTracked", + EventName::ObjectReplicationMissedThreshold => "s3:Replication:OperationMissedThreshold", + EventName::ObjectReplicationReplicatedAfterThreshold => "s3:Replication:OperationReplicatedAfterThreshold", + EventName::ObjectRestoreAll => "s3:ObjectRestore:*", + EventName::ObjectRestorePost => "s3:ObjectRestore:Post", + EventName::ObjectRestoreCompleted => "s3:ObjectRestore:Completed", + EventName::ObjectTransitionAll => "s3:ObjectTransition:*", + EventName::ObjectTransitionFailed => "s3:ObjectTransition:Failed", + EventName::ObjectTransitionComplete => "s3:ObjectTransition:Complete", + EventName::ScannerManyVersions => "s3:Scanner:ManyVersions", + EventName::ScannerLargeVersions => "s3:Scanner:LargeVersions", + EventName::ScannerBigPrefix => "s3:Scanner:BigPrefix", + // Go's String() returns "" for ObjectScannerAll and Everything + EventName::ObjectScannerAll => "s3:Scanner:*", // Follow the pattern in Go Expand + EventName::Everything => "", // Go String() returns "" to unprocessed + } + } + + /// Returns the extended value of the abbreviation event type. + pub fn expand(&self) -> Vec { + match self { + EventName::ObjectAccessedAll => vec![ + EventName::ObjectAccessedGet, + EventName::ObjectAccessedHead, + EventName::ObjectAccessedGetRetention, + EventName::ObjectAccessedGetLegalHold, + EventName::ObjectAccessedAttributes, + ], + EventName::ObjectCreatedAll => vec![ + EventName::ObjectCreatedCompleteMultipartUpload, + EventName::ObjectCreatedCopy, + EventName::ObjectCreatedPost, + EventName::ObjectCreatedPut, + EventName::ObjectCreatedPutRetention, + EventName::ObjectCreatedPutLegalHold, + EventName::ObjectCreatedPutTagging, + EventName::ObjectCreatedDeleteTagging, + ], + EventName::ObjectRemovedAll => vec![ + EventName::ObjectRemovedDelete, + EventName::ObjectRemovedDeleteMarkerCreated, + EventName::ObjectRemovedNoOP, + EventName::ObjectRemovedDeleteAllVersions, + ], + EventName::ObjectReplicationAll => vec![ + EventName::ObjectReplicationFailed, + EventName::ObjectReplicationComplete, + EventName::ObjectReplicationNotTracked, + EventName::ObjectReplicationMissedThreshold, + EventName::ObjectReplicationReplicatedAfterThreshold, + ], + EventName::ObjectRestoreAll => vec![EventName::ObjectRestorePost, EventName::ObjectRestoreCompleted], + EventName::ObjectTransitionAll => vec![EventName::ObjectTransitionFailed, EventName::ObjectTransitionComplete], + EventName::ObjectScannerAll => vec![ + // New + EventName::ScannerManyVersions, + EventName::ScannerLargeVersions, + EventName::ScannerBigPrefix, + ], + EventName::Everything => { + // New + SINGLE_EVENT_NAMES_IN_ORDER.to_vec() + } + // A single type returns to itself directly + _ => vec![*self], + } + } + + /// Returns the mask of type. + /// The compound "All" type will be expanded. + pub fn mask(&self) -> u64 { + let value = *self as u32; + if value > 0 && value <= LAST_SINGLE_TYPE_VALUE { + // It's a single type + 1u64 << (value - 1) + } else { + // It's a compound type + let mut mask = 0u64; + for n in self.expand() { + mask |= n.mask(); // Recursively call mask + } + mask + } + } +} + +impl fmt::Display for EventName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +/// Convert to `EventName` according to string +impl From<&str> for EventName { + fn from(event_str: &str) -> Self { + EventName::parse(event_str).unwrap_or_else(|e| panic!("{}", e)) + } +} diff --git a/crates/targets/src/lib.rs b/crates/targets/src/lib.rs new file mode 100644 index 00000000..a3e35160 --- /dev/null +++ b/crates/targets/src/lib.rs @@ -0,0 +1,35 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod arn; +pub mod error; +mod event_name; +pub mod store; +pub mod target; + +pub use error::{StoreError, TargetError}; +pub use event_name::EventName; +use serde::{Deserialize, Serialize}; +pub use target::Target; + +/// Represents a log of events for sending to targets +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TargetLog { + /// The event name + pub event_name: EventName, + /// The object key + pub key: String, + /// The list of events + pub records: Vec, +} diff --git a/crates/notify/src/store.rs b/crates/targets/src/store.rs similarity index 98% rename from crates/notify/src/store.rs rename to crates/targets/src/store.rs index bf54e1e2..139e32be 100644 --- a/crates/notify/src/store.rs +++ b/crates/targets/src/store.rs @@ -13,7 +13,8 @@ // limitations under the License. use crate::error::StoreError; -use rustfs_config::notify::{COMPRESS_EXT, DEFAULT_EXT, DEFAULT_LIMIT}; +use rustfs_config::DEFAULT_LIMIT; +use rustfs_config::notify::{COMPRESS_EXT, DEFAULT_EXT}; use serde::{Serialize, de::DeserializeOwned}; use snap::raw::{Decoder, Encoder}; use std::sync::{Arc, RwLock}; @@ -123,7 +124,10 @@ pub fn parse_key(s: &str) -> Key { } /// Trait for a store that can store and retrieve items of type T -pub trait Store: Send + Sync { +pub trait Store: Send + Sync +where + T: Send + Sync + 'static + Clone + Serialize, +{ /// The error type for the store type Error; /// The key type for the store diff --git a/crates/notify/src/target/mod.rs b/crates/targets/src/target/mod.rs similarity index 70% rename from crates/notify/src/target/mod.rs rename to crates/targets/src/target/mod.rs index a7913f3a..627fa8d0 100644 --- a/crates/notify/src/target/mod.rs +++ b/crates/targets/src/target/mod.rs @@ -14,8 +14,11 @@ use crate::arn::TargetID; use crate::store::{Key, Store}; -use crate::{Event, StoreError, TargetError}; +use crate::{EventName, StoreError, TargetError}; use async_trait::async_trait; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::fmt::Formatter; use std::sync::Arc; pub mod mqtt; @@ -23,7 +26,10 @@ pub mod webhook; /// Trait for notification targets #[async_trait] -pub trait Target: Send + Sync + 'static { +pub trait Target: Send + Sync + 'static +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ /// Returns the ID of the target fn id(&self) -> TargetID; @@ -36,7 +42,7 @@ pub trait Target: Send + Sync + 'static { async fn is_active(&self) -> Result; /// Saves an event (either sends it immediately or stores it for later) - async fn save(&self, event: Arc) -> Result<(), TargetError>; + async fn save(&self, event: Arc>) -> Result<(), TargetError>; /// Sends an event from the store async fn send_from_store(&self, key: Key) -> Result<(), TargetError>; @@ -45,10 +51,10 @@ pub trait Target: Send + Sync + 'static { async fn close(&self) -> Result<(), TargetError>; /// Returns the store associated with the target (if any) - fn store(&self) -> Option<&(dyn Store + Send + Sync)>; + fn store(&self) -> Option<&(dyn Store, Error = StoreError, Key = Key> + Send + Sync)>; /// Returns the type of the target - fn clone_dyn(&self) -> Box; + fn clone_dyn(&self) -> Box + Send + Sync>; /// Initialize the target, such as establishing a connection, etc. async fn init(&self) -> Result<(), TargetError> { @@ -60,6 +66,17 @@ pub trait Target: Send + Sync + 'static { fn is_enabled(&self) -> bool; } +#[derive(Debug, Serialize, Clone, Deserialize)] +pub struct EntityTarget +where + E: Send + Sync + 'static + Clone + Serialize, +{ + pub object_name: String, + pub bucket_name: String, + pub event_name: EventName, + pub data: E, +} + /// The `ChannelTargetType` enum represents the different types of channel Target /// used in the notification system. /// @@ -75,7 +92,7 @@ pub trait Target: Send + Sync + 'static { /// /// example usage: /// ```rust -/// use rustfs_notify::target::ChannelTargetType; +/// use rustfs_targets::target::ChannelTargetType; /// /// let target_type = ChannelTargetType::Webhook; /// assert_eq!(target_type.as_str(), "webhook"); @@ -101,7 +118,7 @@ impl ChannelTargetType { } impl std::fmt::Display for ChannelTargetType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ChannelTargetType::Webhook => write!(f, "webhook"), ChannelTargetType::Kafka => write!(f, "kafka"), @@ -117,3 +134,28 @@ pub fn parse_bool(value: &str) -> Result { _ => Err(TargetError::ParseError(format!("Unable to parse boolean: {value}"))), } } + +/// `TargetType` enum represents the type of target in the notification system. +#[derive(Debug, Clone)] +pub enum TargetType { + AuditLog, + NotifyEvent, +} + +impl TargetType { + pub fn as_str(&self) -> &'static str { + match self { + TargetType::AuditLog => "audit_log", + TargetType::NotifyEvent => "notify_event", + } + } +} + +impl std::fmt::Display for TargetType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TargetType::AuditLog => write!(f, "audit_log"), + TargetType::NotifyEvent => write!(f, "notify_event"), + } + } +} diff --git a/crates/notify/src/target/mqtt.rs b/crates/targets/src/target/mqtt.rs similarity index 93% rename from crates/notify/src/target/mqtt.rs rename to crates/targets/src/target/mqtt.rs index 9beda90b..9dbf6411 100644 --- a/crates/notify/src/target/mqtt.rs +++ b/crates/targets/src/target/mqtt.rs @@ -13,18 +13,13 @@ // limitations under the License. use crate::store::Key; -use crate::target::ChannelTargetType; -use crate::{ - StoreError, Target, - arn::TargetID, - error::TargetError, - event::{Event, EventLog}, - store::Store, -}; +use crate::target::{ChannelTargetType, EntityTarget, TargetType}; +use crate::{StoreError, Target, TargetLog, arn::TargetID, error::TargetError, store::Store}; use async_trait::async_trait; use rumqttc::{AsyncClient, EventLoop, MqttOptions, Outgoing, Packet, QoS}; use rumqttc::{ConnectionError, mqttbytes::Error as MqttBytesError}; -use rustfs_config::notify::STORE_EXTENSION; +use serde::Serialize; +use serde::de::DeserializeOwned; use std::sync::Arc; use std::{ path::PathBuf, @@ -62,6 +57,8 @@ pub struct MQTTArgs { pub queue_dir: String, /// The maximum number of events to store pub queue_limit: u64, + /// the target type + pub target_type: TargetType, } impl MQTTArgs { @@ -77,6 +74,10 @@ impl MQTTArgs { } } + if self.topic.is_empty() { + return Err(TargetError::Configuration("MQTT topic cannot be empty".to_string())); + } + if !self.queue_dir.is_empty() { let path = std::path::Path::new(&self.queue_dir); if !path.is_absolute() { @@ -100,16 +101,22 @@ struct BgTaskManager { } /// A target that sends events to an MQTT broker -pub struct MQTTTarget { +pub struct MQTTTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ id: TargetID, args: MQTTArgs, client: Arc>>, - store: Option + Send + Sync>>, + store: Option, Error = StoreError, Key = Key> + Send + Sync>>, connected: Arc, bg_task_manager: Arc, } -impl MQTTTarget { +impl MQTTTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ /// Creates a new MQTTTarget #[instrument(skip(args), fields(target_id_as_string = %id))] pub fn new(id: String, args: MQTTArgs) -> Result { @@ -121,7 +128,12 @@ impl MQTTTarget { // Ensure the directory name is valid for filesystem let specific_queue_path = base_path.join(unique_dir_name); debug!(target_id = %target_id, path = %specific_queue_path.display(), "Initializing queue store for MQTT target"); - let store = crate::store::QueueStore::::new(specific_queue_path, args.queue_limit, STORE_EXTENSION); + let extension = match args.target_type { + TargetType::AuditLog => rustfs_config::audit::AUDIT_STORE_EXTENSION, + TargetType::NotifyEvent => rustfs_config::notify::STORE_EXTENSION, + }; + + let store = crate::store::QueueStore::>::new(specific_queue_path, args.queue_limit, extension); if let Err(e) = store.open() { error!( target_id = %target_id, @@ -130,7 +142,7 @@ impl MQTTTarget { ); return Err(TargetError::Storage(format!("{e}"))); } - Some(Box::new(store) as Box + Send + Sync>) + Some(Box::new(store) as Box, Error = StoreError, Key = Key> + Send + Sync>) } else { None }; @@ -237,18 +249,18 @@ impl MQTTTarget { } #[instrument(skip(self, event), fields(target_id = %self.id))] - async fn send(&self, event: &Event) -> Result<(), TargetError> { + async fn send(&self, event: &EntityTarget) -> Result<(), TargetError> { let client_guard = self.client.lock().await; let client = client_guard .as_ref() .ok_or_else(|| TargetError::Configuration("MQTT client not initialized".to_string()))?; - let object_name = urlencoding::decode(&event.s3.object.key) + let object_name = urlencoding::decode(&event.object_name) .map_err(|e| TargetError::Encoding(format!("Failed to decode object key: {e}")))?; - let key = format!("{}/{}", event.s3.bucket.name, object_name); + let key = format!("{}/{}", event.bucket_name, object_name); - let log = EventLog { + let log = TargetLog { event_name: event.event_name, key, records: vec![event.clone()], @@ -256,7 +268,6 @@ impl MQTTTarget { let data = serde_json::to_vec(&log).map_err(|e| TargetError::Serialization(format!("Failed to serialize event: {e}")))?; - // Vec Convert to String, only for printing logs let data_string = String::from_utf8(data.clone()) .map_err(|e| TargetError::Encoding(format!("Failed to convert event data to UTF-8: {e}")))?; debug!("Sending event to mqtt target: {}, event log: {}", self.id, data_string); @@ -278,7 +289,7 @@ impl MQTTTarget { Ok(()) } - pub fn clone_target(&self) -> Box { + pub fn clone_target(&self) -> Box + Send + Sync> { Box::new(MQTTTarget { id: self.id.clone(), args: self.args.clone(), @@ -444,7 +455,10 @@ fn is_fatal_mqtt_error(err: &ConnectionError) -> bool { } #[async_trait] -impl Target for MQTTTarget { +impl Target for MQTTTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ fn id(&self) -> TargetID { self.id.clone() } @@ -476,7 +490,7 @@ impl Target for MQTTTarget { } #[instrument(skip(self, event), fields(target_id = %self.id))] - async fn save(&self, event: Arc) -> Result<(), TargetError> { + async fn save(&self, event: Arc>) -> Result<(), TargetError> { if let Some(store) = &self.store { debug!(target_id = %self.id, "Event saved to store start"); // If store is configured, ONLY put the event into the store. @@ -620,11 +634,11 @@ impl Target for MQTTTarget { Ok(()) } - fn store(&self) -> Option<&(dyn Store + Send + Sync)> { + fn store(&self) -> Option<&(dyn Store, Error = StoreError, Key = Key> + Send + Sync)> { self.store.as_deref() } - fn clone_dyn(&self) -> Box { + fn clone_dyn(&self) -> Box + Send + Sync> { self.clone_target() } diff --git a/crates/notify/src/target/webhook.rs b/crates/targets/src/target/webhook.rs similarity index 90% rename from crates/notify/src/target/webhook.rs rename to crates/targets/src/target/webhook.rs index 0b3f93de..8ae03888 100644 --- a/crates/notify/src/target/webhook.rs +++ b/crates/targets/src/target/webhook.rs @@ -12,17 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::target::ChannelTargetType; +use crate::target::{ChannelTargetType, EntityTarget, TargetType}; use crate::{ - StoreError, Target, + StoreError, Target, TargetLog, arn::TargetID, error::TargetError, - event::{Event, EventLog}, store::{Key, Store}, }; use async_trait::async_trait; use reqwest::{Client, StatusCode, Url}; use rustfs_config::notify::STORE_EXTENSION; +use serde::Serialize; +use serde::de::DeserializeOwned; use std::{ path::PathBuf, sync::{ @@ -53,6 +54,8 @@ pub struct WebhookArgs { pub client_cert: String, /// The client key for TLS (PEM format) pub client_key: String, + /// the target type + pub target_type: TargetType, } impl WebhookArgs { @@ -84,20 +87,26 @@ impl WebhookArgs { } /// A target that sends events to a webhook -pub struct WebhookTarget { +pub struct WebhookTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ id: TargetID, args: WebhookArgs, http_client: Arc, // Add Send + Sync constraints to ensure thread safety - store: Option + Send + Sync>>, + store: Option, Error = StoreError, Key = Key> + Send + Sync>>, initialized: AtomicBool, addr: String, cancel_sender: mpsc::Sender<()>, } -impl WebhookTarget { +impl WebhookTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ /// Clones the WebhookTarget, creating a new instance with the same configuration - pub fn clone_box(&self) -> Box { + pub fn clone_box(&self) -> Box + Send + Sync> { Box::new(WebhookTarget { id: self.id.clone(), args: self.args.clone(), @@ -144,7 +153,7 @@ impl WebhookTarget { let queue_store = if !args.queue_dir.is_empty() { let queue_dir = PathBuf::from(&args.queue_dir).join(format!("rustfs-{}-{}", ChannelTargetType::Webhook.as_str(), target_id.id)); - let store = crate::store::QueueStore::::new(queue_dir, args.queue_limit, STORE_EXTENSION); + let store = crate::store::QueueStore::>::new(queue_dir, args.queue_limit, STORE_EXTENSION); if let Err(e) = store.open() { error!("Failed to open store for Webhook target {}: {}", target_id.id, e); @@ -152,7 +161,7 @@ impl WebhookTarget { } // Make sure that the Store trait implemented by QueueStore matches the expected error type - Some(Box::new(store) as Box + Send + Sync>) + Some(Box::new(store) as Box, Error = StoreError, Key = Key> + Send + Sync>) } else { None }; @@ -203,17 +212,17 @@ impl WebhookTarget { Ok(()) } - async fn send(&self, event: &Event) -> Result<(), TargetError> { + async fn send(&self, event: &EntityTarget) -> Result<(), TargetError> { info!("Webhook Sending event to webhook target: {}", self.id); - let object_name = urlencoding::decode(&event.s3.object.key) + let object_name = urlencoding::decode(&event.object_name) .map_err(|e| TargetError::Encoding(format!("Failed to decode object key: {e}")))?; - let key = format!("{}/{}", event.s3.bucket.name, object_name); + let key = format!("{}/{}", event.bucket_name, object_name); - let log = EventLog { + let log = TargetLog { event_name: event.event_name, key, - records: vec![event.clone()], + records: vec![event.data.clone()], }; let data = serde_json::to_vec(&log).map_err(|e| TargetError::Serialization(format!("Failed to serialize event: {e}")))?; @@ -275,12 +284,14 @@ impl WebhookTarget { } #[async_trait] -impl Target for WebhookTarget { +impl Target for WebhookTarget +where + E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned, +{ fn id(&self) -> TargetID { self.id.clone() } - // Make sure Future is Send async fn is_active(&self) -> Result { let socket_addr = lookup_host(&self.addr) .await @@ -305,7 +316,7 @@ impl Target for WebhookTarget { } } - async fn save(&self, event: Arc) -> Result<(), TargetError> { + async fn save(&self, event: Arc>) -> Result<(), TargetError> { if let Some(store) = &self.store { // Call the store method directly, no longer need to acquire the lock store @@ -379,17 +390,15 @@ impl Target for WebhookTarget { Ok(()) } - fn store(&self) -> Option<&(dyn Store + Send + Sync)> { + fn store(&self) -> Option<&(dyn Store, Error = StoreError, Key = Key> + Send + Sync)> { // Returns the reference to the internal store self.store.as_deref() } - fn clone_dyn(&self) -> Box { + fn clone_dyn(&self) -> Box + Send + Sync> { self.clone_box() } - // The existing init method can meet the needs well, but we need to make sure it complies with the Target trait - // We can use the existing init method, but adjust the return value to match the trait requirement async fn init(&self) -> Result<(), TargetError> { // If the target is disabled, return to success directly if !self.is_enabled() { diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 6d753eff..a72746f2 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -49,8 +49,10 @@ rustfs-config = { workspace = true, features = ["constants", "notify"] } rustfs-notify = { workspace = true } rustfs-obs = { workspace = true } rustfs-utils = { workspace = true, features = ["full"] } -rustfs-protos.workspace = true +rustfs-protos = { workspace = true } rustfs-s3select-query = { workspace = true } +rustfs-audit-logger = { workspace = true } +rustfs-targets = { workspace = true } atoi = { workspace = true } atomic_enum = { workspace = true } axum.workspace = true diff --git a/rustfs/src/admin/handlers/event.rs b/rustfs/src/admin/handlers/event.rs index 99959744..f4ee2d50 100644 --- a/rustfs/src/admin/handlers/event.rs +++ b/rustfs/src/admin/handlers/event.rs @@ -18,9 +18,10 @@ use crate::admin::router::Operation; use crate::auth::{check_key_valid, get_session_token}; use http::{HeaderMap, StatusCode}; use matchit::Params; -use rustfs_config::notify::{ENABLE_KEY, ENABLE_ON, NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS}; -use rustfs_notify::EventName; +use rustfs_config::notify::{NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS}; +use rustfs_config::{ENABLE_KEY, EnableState}; use rustfs_notify::rules::{BucketNotificationConfig, PatternRules}; +use rustfs_targets::EventName; use s3s::header::CONTENT_LENGTH; use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error}; use serde::{Deserialize, Serialize}; @@ -78,7 +79,7 @@ impl Operation for SetNotificationTarget { .map_err(|e| s3_error!(InvalidArgument, "invalid json body for target config: {}", e))?; // If there is an enable key, add an enable key value to "on" if !kvs_map.contains_key(ENABLE_KEY) { - kvs_map.insert(ENABLE_KEY.to_string(), ENABLE_ON.to_string()); + kvs_map.insert(ENABLE_KEY.to_string(), EnableState::On.to_string()); } let kvs = rustfs_ecstore::config::KVS( diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs index 0449b290..2fde47f9 100644 --- a/rustfs/src/admin/mod.rs +++ b/rustfs/src/admin/mod.rs @@ -25,7 +25,10 @@ use handlers::{ sts, tier, user, }; -use crate::admin::handlers::event::{ListNotificationTargets, RemoveNotificationTarget, SetNotificationTarget}; +use crate::admin::handlers::event::{ + GetBucketNotification, ListNotificationTargets, RemoveBucketNotification, RemoveNotificationTarget, SetBucketNotification, + SetNotificationTarget, +}; use handlers::{GetReplicationMetricsHandler, ListRemoteTargetHandler, RemoveRemoteTargetHandler, SetRemoteTargetHandler}; use hyper::Method; use router::{AdminOperation, S3Router}; @@ -389,5 +392,23 @@ fn register_user_route(r: &mut S3Router) -> std::io::Result<()> AdminOperation(&RemoveNotificationTarget {}), )?; + r.insert( + Method::POST, + format!("{}{}", ADMIN_PREFIX, "/v3/target-set-bucket").as_str(), + AdminOperation(&SetBucketNotification {}), + )?; + + r.insert( + Method::POST, + format!("{}{}", ADMIN_PREFIX, "/v3/target-get-bucket").as_str(), + AdminOperation(&GetBucketNotification {}), + )?; + + r.insert( + Method::POST, + format!("{}{}", ADMIN_PREFIX, "/v3/target-remove-bucket").as_str(), + AdminOperation(&RemoveBucketNotification {}), + )?; + Ok(()) } diff --git a/rustfs/src/config/mod.rs b/rustfs/src/config/mod.rs index 038f5f76..ddc248ce 100644 --- a/rustfs/src/config/mod.rs +++ b/rustfs/src/config/mod.rs @@ -72,7 +72,7 @@ pub struct Opt { #[arg(long, default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(), env = "RUSTFS_OBS_ENDPOINT")] pub obs_endpoint: String, - /// tls path for rustfs api and console. + /// tls path for rustfs API and console. #[arg(long, env = "RUSTFS_TLS_PATH")] pub tls_path: Option, diff --git a/rustfs/src/server/audit.rs b/rustfs/src/server/audit.rs new file mode 100644 index 00000000..439d7a7c --- /dev/null +++ b/rustfs/src/server/audit.rs @@ -0,0 +1,13 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/rustfs/src/server/mod.rs b/rustfs/src/server/mod.rs index 5efd8617..3b86e513 100644 --- a/rustfs/src/server/mod.rs +++ b/rustfs/src/server/mod.rs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod audit; mod http; mod hybrid; mod layer; mod service_state; + pub(crate) use http::start_http_server; pub(crate) use service_state::SHUTDOWN_TIMEOUT; pub(crate) use service_state::ServiceState; diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index e34570dc..6c8f7d01 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -76,7 +76,6 @@ use rustfs_ecstore::store_api::PutObjReader; use rustfs_ecstore::store_api::StorageAPI; use rustfs_filemeta::headers::RESERVED_METADATA_PREFIX_LOWER; use rustfs_filemeta::headers::{AMZ_DECODED_CONTENT_LENGTH, AMZ_OBJECT_TAGGING}; -use rustfs_notify::EventName; use rustfs_policy::auth; use rustfs_policy::policy::action::Action; use rustfs_policy::policy::action::S3Action; @@ -86,6 +85,7 @@ use rustfs_rio::EtagReader; use rustfs_rio::HashReader; use rustfs_rio::Reader; use rustfs_rio::WarpReader; +use rustfs_targets::EventName; use rustfs_utils::CompressionAlgorithm; use rustfs_utils::path::path_join_buf; use rustfs_zip::CompressionFormat;