Support Azure blob storage in a similar fashion to S3. (#907)

This commit is contained in:
tamwuff 2024-11-10 02:45:50 -05:00 committed by GitHub
parent 9742149ce8
commit 9b4f5e8c75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 707 additions and 57 deletions

502
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "RustyXML"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5ace29ee3216de37c0546865ad08edef58b0f9e76838ed8959a84a990e58c5"
[[package]]
name = "addr2line"
version = "0.24.2"
@ -74,7 +80,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom",
"getrandom 0.2.15",
"once_cell",
"version_check",
]
@ -86,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"getrandom 0.2.15",
"once_cell",
"serde",
"version_check",
@ -304,6 +310,17 @@ dependencies = [
"syn 2.0.79",
]
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-compression"
version = "0.4.15"
@ -317,6 +334,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.3.1",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-recursion"
version = "1.1.1"
@ -461,6 +489,92 @@ dependencies = [
"tower-service",
]
[[package]]
name = "azure_core"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b552ad43a45a746461ec3d3a51dfb6466b4759209414b439c165eb6a6b7729e"
dependencies = [
"async-trait",
"base64 0.22.1",
"bytes",
"dyn-clone",
"futures",
"getrandom 0.2.15",
"hmac 0.12.1",
"http-types",
"once_cell",
"paste",
"pin-project",
"quick-xml 0.31.0",
"rand 0.8.5",
"reqwest 0.12.8",
"rustc_version 0.4.1",
"serde",
"serde_json",
"sha2 0.10.8",
"time",
"tracing",
"url",
"uuid",
]
[[package]]
name = "azure_storage"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f838159f4d29cb400a14d9d757578ba495ae64feb07a7516bf9e4415127126"
dependencies = [
"RustyXML",
"async-lock",
"async-trait",
"azure_core",
"bytes",
"serde",
"serde_derive",
"time",
"tracing",
"url",
"uuid",
]
[[package]]
name = "azure_storage_blobs"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97e83c3636ae86d9a6a7962b2112e3b19eb3903915c50ce06ff54ff0a2e6a7e4"
dependencies = [
"RustyXML",
"azure_core",
"azure_storage",
"azure_svc_blobstorage",
"bytes",
"futures",
"serde",
"serde_derive",
"serde_json",
"time",
"tracing",
"url",
"uuid",
]
[[package]]
name = "azure_svc_blobstorage"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e6c6f20c5611b885ba94c7bae5e02849a267381aecb8aee577e8c35ff4064c6"
dependencies = [
"azure_core",
"bytes",
"futures",
"log",
"once_cell",
"serde",
"serde_json",
"time",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -1070,7 +1184,7 @@ dependencies = [
"idna 1.0.2",
"imagesize",
"imap_proto",
"infer",
"infer 0.16.0",
"jmap_proto",
"libc",
"mail-auth",
@ -1118,6 +1232,15 @@ dependencies = [
"zip",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.15.8"
@ -1152,7 +1275,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom",
"getrandom 0.2.15",
"once_cell",
"tiny-keccak",
]
@ -1299,7 +1422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array 0.14.7",
"rand_core",
"rand_core 0.6.4",
"subtle",
"zeroize",
]
@ -1311,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.7",
"rand_core",
"rand_core 0.6.4",
"typenum",
]
@ -1512,7 +1635,7 @@ checksum = "1ab8a4ea925ce79678034870834602a2980f4b88c09e97feb266496dbb4493d2"
dependencies = [
"async-trait",
"deadpool 0.12.1",
"getrandom",
"getrandom 0.2.15",
"tokio",
"tokio-postgres",
"tracing",
@ -1860,7 +1983,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand_core",
"rand_core 0.6.4",
"serde",
"sha2 0.10.8",
"subtle",
@ -1908,7 +2031,7 @@ dependencies = [
"hkdf",
"pem-rfc7468",
"pkcs8",
"rand_core",
"rand_core 0.6.4",
"sec1",
"subtle",
"zeroize",
@ -1968,6 +2091,43 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener 5.3.1",
"pin-project-lite",
]
[[package]]
name = "event_macro"
version = "0.1.0"
@ -2018,13 +2178,28 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fastrand"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]]
name = "ff"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@ -2116,7 +2291,7 @@ dependencies = [
"foundationdb-sys",
"futures",
"memchr",
"rand",
"rand 0.8.5",
"serde",
"serde_bytes",
"serde_json",
@ -2265,6 +2440,21 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.31"
@ -2345,6 +2535,17 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.15"
@ -2354,7 +2555,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@ -2387,7 +2588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@ -2511,7 +2712,7 @@ dependencies = [
"hickory-proto",
"once_cell",
"radix_trie",
"rand",
"rand 0.8.5",
"rustls 0.21.12",
"thiserror",
"tokio",
@ -2537,7 +2738,7 @@ dependencies = [
"idna 0.4.0",
"ipnet",
"once_cell",
"rand",
"rand 0.8.5",
"ring 0.16.20",
"rustls 0.21.12",
"rustls-pemfile 1.0.4",
@ -2562,7 +2763,7 @@ dependencies = [
"lru-cache",
"once_cell",
"parking_lot",
"rand",
"rand 0.8.5",
"resolv-conf",
"rustls 0.21.12",
"smallvec",
@ -2687,6 +2888,26 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-types"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
dependencies = [
"anyhow",
"async-channel",
"base64 0.13.1",
"futures-lite",
"infer 0.2.3",
"pin-project-lite",
"rand 0.7.3",
"serde",
"serde_json",
"serde_qs",
"serde_urlencoded",
"url",
]
[[package]]
name = "httparse"
version = "1.9.5"
@ -2795,6 +3016,22 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.5.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.9"
@ -3024,7 +3261,7 @@ dependencies = [
"md5",
"nlp",
"parking_lot",
"rand",
"rand 0.8.5",
"rustls 0.23.14",
"rustls-pemfile 2.2.0",
"store",
@ -3080,6 +3317,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "infer"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
[[package]]
name = "infer"
version = "0.16.0"
@ -3255,7 +3498,7 @@ dependencies = [
"p256",
"pkcs8",
"quick-xml 0.36.2",
"rand",
"rand 0.8.5",
"rasn",
"rasn-cms",
"rasn-pkix",
@ -3521,6 +3764,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
version = "0.7.3"
@ -3607,7 +3856,7 @@ dependencies = [
"mail-parser",
"parking_lot",
"quick-xml 0.36.2",
"rand",
"rand 0.8.5",
"ring 0.17.8",
"rsa",
"rustls-pemfile 2.2.0",
@ -3823,7 +4072,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@ -3866,7 +4115,7 @@ dependencies = [
"pem",
"percent-encoding",
"pin-project",
"rand",
"rand 0.8.5",
"rustls 0.22.4",
"rustls-pemfile 2.2.0",
"serde",
@ -3905,7 +4154,7 @@ dependencies = [
"mysql-common-derive",
"num-bigint",
"num-traits",
"rand",
"rand 0.8.5",
"regex",
"rust_decimal",
"saturating",
@ -3921,6 +4170,23 @@ dependencies = [
"zstd",
]
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
@ -4009,7 +4275,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand",
"rand 0.8.5",
"smallvec",
"zeroize",
]
@ -4228,7 +4494,7 @@ dependencies = [
"once_cell",
"opentelemetry",
"percent-encoding",
"rand",
"rand 0.8.5",
"serde_json",
"thiserror",
]
@ -4277,10 +4543,16 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"rand_core",
"rand_core 0.6.4",
"sha2 0.10.8",
]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -4311,7 +4583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@ -4395,7 +4667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared 0.11.2",
"rand",
"rand 0.8.5",
]
[[package]]
@ -4538,7 +4810,7 @@ dependencies = [
"hmac 0.12.1",
"md-5 0.10.6",
"memchr",
"rand",
"rand 0.8.5",
"sha2 0.10.8",
"stringprep",
]
@ -4761,7 +5033,7 @@ dependencies = [
"byteorder",
"hmac 0.10.1",
"md-5 0.9.1",
"rand",
"rand 0.8.5",
"sha-1",
"sha2 0.9.9",
]
@ -4772,6 +5044,16 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "quick-xml"
version = "0.32.0"
@ -4816,7 +5098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
dependencies = [
"bytes",
"rand",
"rand 0.8.5",
"ring 0.17.8",
"rustc-hash 2.0.0",
"rustls 0.23.14",
@ -4875,6 +5157,19 @@ dependencies = [
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -4882,8 +5177,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
@ -4893,7 +5198,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
@ -4902,7 +5216,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.15",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
@ -5024,7 +5347,7 @@ dependencies = [
"num-bigint",
"percent-encoding",
"pin-project-lite",
"rand",
"rand 0.8.5",
"rustls 0.23.14",
"rustls-native-certs 0.7.3",
"rustls-pemfile 2.2.0",
@ -5054,7 +5377,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"getrandom 0.2.15",
"libredox",
"thiserror",
]
@ -5158,12 +5481,14 @@ dependencies = [
"http-body-util",
"hyper 1.5.0",
"hyper-rustls 0.27.3",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"mime_guess",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
@ -5176,6 +5501,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.0",
"tokio-util",
"tower-service",
@ -5240,7 +5566,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"getrandom 0.2.15",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
@ -5329,7 +5655,7 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
@ -5434,7 +5760,7 @@ dependencies = [
"borsh",
"bytes",
"num-traits",
"rand",
"rand 0.8.5",
"rkyv",
"serde",
"serde_json",
@ -5485,6 +5811,19 @@ dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.21.12"
@ -5783,7 +6122,7 @@ dependencies = [
"ecdsa",
"ed25519",
"ed25519-dalek",
"getrandom",
"getrandom 0.2.15",
"idea",
"idna 1.0.2",
"lalrpop",
@ -5797,8 +6136,8 @@ dependencies = [
"p256",
"p384",
"p521",
"rand",
"rand_core",
"rand 0.8.5",
"rand_core 0.6.4",
"regex",
"regex-syntax",
"ripemd",
@ -5854,6 +6193,17 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_qs"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
dependencies = [
"percent-encoding",
"serde",
"thiserror",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -6015,7 +6365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest 0.10.7",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
@ -6081,7 +6431,7 @@ dependencies = [
"nlp",
"num_cpus",
"parking_lot",
"rand",
"rand 0.8.5",
"rayon",
"regex",
"reqwest 0.12.8",
@ -6190,7 +6540,7 @@ dependencies = [
"num_cpus",
"prettytable-rs",
"pwhash",
"rand",
"rand 0.8.5",
"reqwest 0.12.8",
"rpassword",
"serde",
@ -6211,6 +6561,9 @@ dependencies = [
"ahash 0.8.11",
"arc-swap",
"async-trait",
"azure_core",
"azure_storage",
"azure_storage_blobs",
"bincode",
"bitpacking",
"blake3",
@ -6229,10 +6582,11 @@ dependencies = [
"num_cpus",
"parking_lot",
"r2d2",
"rand",
"rand 0.8.5",
"rayon",
"redis",
"regex",
"reqwest 0.12.8",
"ring 0.17.8",
"roaring",
"rocksdb",
@ -6401,6 +6755,19 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
dependencies = [
"cfg-if",
"fastrand 2.2.0",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "term"
version = "0.7.0"
@ -6502,6 +6869,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"js-sys",
"num-conv",
"powerfmt",
"serde",
@ -6599,6 +6967,16 @@ dependencies = [
"syn 2.0.79",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-postgres"
version = "0.7.12"
@ -6618,7 +6996,7 @@ dependencies = [
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand",
"rand 0.8.5",
"socket2",
"tokio",
"tokio-util",
@ -6782,7 +7160,7 @@ dependencies = [
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
@ -6896,7 +7274,7 @@ dependencies = [
"http 1.1.0",
"httparse",
"log",
"rand",
"rand 0.8.5",
"rustls 0.22.4",
"rustls-pki-types",
"sha1",
@ -6917,7 +7295,7 @@ dependencies = [
"http 1.1.0",
"httparse",
"log",
"rand",
"rand 0.8.5",
"sha1",
"thiserror",
"utf-8",
@ -6939,7 +7317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"rand",
"rand 0.8.5",
"static_assertions",
]
@ -7059,6 +7437,7 @@ dependencies = [
"form_urlencoded",
"idna 0.5.0",
"percent-encoding",
"serde",
]
[[package]]
@ -7109,7 +7488,7 @@ dependencies = [
"parking_lot",
"pem",
"privdrop",
"rand",
"rand 0.8.5",
"rcgen 0.13.1",
"regex",
"reqwest 0.12.8",
@ -7133,7 +7512,8 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"getrandom 0.2.15",
"serde",
]
[[package]]
@ -7154,6 +7534,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
[[package]]
name = "walkdir"
version = "2.5.0"
@ -7173,6 +7559,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -7602,7 +7994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [
"curve25519-dalek",
"rand_core",
"rand_core 0.6.4",
"zeroize",
]
@ -7789,7 +8181,7 @@ dependencies = [
"lzma-rs",
"memchr",
"pbkdf2",
"rand",
"rand 0.8.5",
"sha1",
"thiserror",
"time",

View file

@ -34,7 +34,7 @@ tokio = { version = "1.23", features = ["full"] }
jemallocator = "0.5.0"
[features]
default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "enterprise"]
default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "azure", "enterprise"]
#default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "foundationdb", "enterprise"]
sqlite = ["store/sqlite"]
foundationdb = ["store/foundation", "common/foundation"]
@ -44,4 +44,5 @@ rocks = ["store/rocks"]
elastic = ["store/elastic"]
s3 = ["store/s3"]
redis = ["store/redis"]
azure = ["store/azure"]
enterprise = ["jmap/enterprise", "common/enterprise", "store/enterprise", "managesieve/enterprise", "directory/enterprise"]

View file

@ -12,6 +12,10 @@ rocksdb = { version = "0.22", optional = true, features = ["multi-threaded-cf"]
foundationdb = { version = "0.9.0", features = ["embedded-fdb-include", "fdb-7_1"], optional = true }
rusqlite = { version = "0.32", features = ["bundled"], optional = true }
rust-s3 = { version = "=0.35.0-alpha.2", default-features = false, features = ["tokio-rustls-tls", "no-verify-ssl"], optional = true }
azure_core = { version = "0.21.0", optional = true }
azure_storage = { version = "0.21.0", optional = true }
azure_storage_blobs = { version = "0.21.0", optional = true }
reqwest = { version = "0.12.0", default-features = false, optional = true }
tokio = { version = "1.23", features = ["sync", "fs", "io-util"] }
r2d2 = { version = "0.8.10", optional = true }
futures = { version = "0.3", optional = true }
@ -56,6 +60,7 @@ postgres = ["tokio-postgres", "deadpool-postgres", "tokio-rustls", "rustls", "ri
elastic = ["elasticsearch", "serde_json"]
mysql = ["mysql_async", "futures"]
s3 = ["rust-s3"]
azure = ["azure_core", "azure_storage", "azure_storage_blobs", "reqwest"]
foundation = ["foundationdb", "futures"]
fdb-chunked-bm = []
redis = ["dep:redis", "deadpool"]

View file

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{fmt::Display, io::Write, ops::Range, time::Duration};
use azure_core::error::ErrorKind;
use azure_core::{ExponentialRetryOptions, RetryOptions, StatusCode, TransportOptions};
use azure_storage::StorageCredentials;
use azure_storage_blobs::prelude::{ClientBuilder, ContainerClient};
use futures::stream::StreamExt;
use std::sync::Arc;
use utils::{
codec::base32_custom::Base32Writer,
config::{utils::AsKey, Config},
};
pub struct AzureStore {
client: ContainerClient,
prefix: Option<String>,
}
impl AzureStore {
pub async fn open(config: &mut Config, prefix: impl AsKey) -> Option<Self> {
let prefix = prefix.as_key();
let storage_account = config
.value_require((&prefix, "storage-account"))?
.to_string();
let container = config.value_require((&prefix, "container"))?.to_string();
let credentials = match (
config.value((&prefix, "access-key")),
config.value((&prefix, "sas-token")),
) {
(Some(access_key), None) => {
StorageCredentials::access_key(storage_account.clone(), access_key.to_string())
}
(None, Some(sas_token)) => match StorageCredentials::sas_token(sas_token) {
Ok(cred) => cred,
Err(err) => {
config.new_build_error(
prefix.as_str(),
format!("Failed to create credentials: {err:?}"),
);
return None;
}
},
_ => {
config.new_build_error(
prefix.as_str(),
format!("Failed to create credentials: exactly one of 'access-key' and 'sas-token' must be specified"));
return None;
}
};
let timeout = config
.property_or_default::<Duration>((&prefix, "timeout"), "30s")
.unwrap_or_else(|| Duration::from_secs(30));
let transport = match reqwest::Client::builder().timeout(timeout).build() {
Ok(client) => Arc::new(client),
Err(err) => {
config.new_build_error(
prefix.as_str(),
format!("Failed to create HTTP client: {err:?}"),
);
return None;
}
};
// Take the configured number of retries and multiply by 2. This is intended to match the
// precedent set by the S3 back end, where we do the indicated number of retries,
// ourselves, but internally the rust-s3 crate is also retrying each of our requests up to
// one additional time, itself. So our retries, and the S3 backend's retries, are
// comparable to each other.
let max_retries: u32 = config
.property_or_default((&prefix, "max-retries"), "3")
.unwrap_or(3)
* 2;
Some(AzureStore {
client: ClientBuilder::new(storage_account, credentials)
.transport(TransportOptions::new(transport))
.retry(RetryOptions::exponential(
ExponentialRetryOptions::default().max_retries(max_retries),
))
.container_client(container),
prefix: config.value((&prefix, "key-prefix")).map(|s| s.to_string()),
})
}
pub(crate) async fn get_blob(
&self,
key: &[u8],
range: Range<usize>,
) -> trc::Result<Option<Vec<u8>>> {
let blob_client = self.client.blob_client(self.build_key(key));
let mut stream = blob_client.get();
let mut buf = if range.end == usize::MAX {
// Let's turn this into a proper RangeFrom.
stream = stream.range(range.start..);
// We don't know how big to expect the result to be.
Vec::new()
} else {
stream = stream.range(range.clone());
Vec::with_capacity(range.end - range.start)
};
let mut stream = stream.into_stream();
while let Some(response) = stream.next().await {
let err = match response {
Ok(chunks) => {
let mut chunks = chunks.data;
let mut err = None;
while let Some(chunk) = chunks.next().await {
match chunk {
Ok(ref data) => {
buf.extend(data);
}
Err(e) => {
err = Some(e);
}
}
}
err
}
Err(e) => Some(e),
};
if let Some(e) = err {
return if matches!(
e.kind(),
ErrorKind::HttpResponse {
status: StatusCode::NotFound,
..
}
) {
Ok(None)
} else {
Err(trc::StoreEvent::S3Error.reason(e))
};
}
}
Ok(Some(buf))
}
pub(crate) async fn put_blob(&self, key: &[u8], data: &[u8]) -> trc::Result<()> {
let blob_client = self.client.blob_client(self.build_key(key));
// We unfortunately have to make a copy of `data`. This is because the Azure SDK wants to
// coerce the body into a value of type azure_core::Body, which doesn't have a lifetime
// parameter and so cannot hold any non-static references (directly or indirectly).
let data = data.to_vec();
blob_client
.put_block_blob(data)
.into_future()
.await
.map_err(into_error)?;
Ok(())
}
pub(crate) async fn delete_blob(&self, key: &[u8]) -> trc::Result<bool> {
let blob_client = self.client.blob_client(self.build_key(key));
if let Err(e) = blob_client.delete().into_future().await {
if matches!(
e.kind(),
ErrorKind::HttpResponse {
status: StatusCode::NotFound,
..
}
) {
Ok(false)
} else {
Err(trc::StoreEvent::S3Error.reason(e))
}
} else {
Ok(true)
}
}
fn build_key(&self, key: &[u8]) -> String {
if let Some(prefix) = &self.prefix {
let mut writer =
Base32Writer::with_raw_capacity(prefix.len() + ((key.len() + 3) / 4 * 5));
writer.push_string(prefix);
writer.write_all(key).unwrap();
writer.finalize()
} else {
Base32Writer::from_bytes(key).finalize()
}
}
}
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
trc::StoreEvent::S3Error.reason(err)
}

View file

@ -81,6 +81,8 @@ impl DistributedBlob {
BlobBackend::Fs(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.get_blob(key, read_range).await,
BlobBackend::Composite(_) => unimplemented!(),
}
})
@ -111,6 +113,8 @@ impl DistributedBlob {
BlobBackend::Fs(store) => store.put_blob(key, data).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.put_blob(key, data).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.put_blob(key, data).await,
BlobBackend::Composite(_) => unimplemented!(),
}
})
@ -141,6 +145,8 @@ impl DistributedBlob {
BlobBackend::Fs(store) => store.delete_blob(key).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.delete_blob(key).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.delete_blob(key).await,
BlobBackend::Composite(_) => unimplemented!(),
}
})

View file

@ -24,6 +24,8 @@ pub mod rocksdb;
pub mod s3;
#[cfg(feature = "sqlite")]
pub mod sqlite;
#[cfg(feature = "azure")]
pub mod azure;
pub const MAX_TOKEN_LENGTH: usize = (u8::MAX >> 1) as usize;
pub const MAX_TOKEN_MASK: usize = MAX_TOKEN_LENGTH - 1;

View file

@ -38,6 +38,9 @@ use crate::backend::elastic::ElasticSearchStore;
#[cfg(feature = "redis")]
use crate::backend::redis::RedisStore;
#[cfg(feature = "azure")]
use crate::backend::azure::AzureStore;
impl Stores {
pub async fn parse_all(config: &mut Config) -> Self {
let mut stores = Self::parse(config).await;
@ -216,6 +219,13 @@ impl Stores {
"sql-read-replica" | "distributed-blob" => {
composite_stores.push((store_id, protocol));
}
#[cfg(feature = "azure")]
"azure" => {
if let Some(db) = AzureStore::open(config, prefix).await.map(BlobStore::from) {
self.blob_stores
.insert(store_id, db.with_compression(compression_algo));
}
}
unknown => {
config.new_parse_warning(
("store", id, "type"),

View file

@ -37,6 +37,8 @@ impl BlobStore {
BlobBackend::Fs(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "enterprise")]
BlobBackend::Composite(store) => store.get_blob(key, read_range).await,
};
@ -117,6 +119,8 @@ impl BlobStore {
BlobBackend::Fs(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "enterprise")]
BlobBackend::Composite(store) => store.put_blob(key, data.as_ref()).await,
}
@ -153,6 +157,8 @@ impl BlobStore {
BlobBackend::Fs(store) => store.delete_blob(key).await,
#[cfg(feature = "s3")]
BlobBackend::S3(store) => store.delete_blob(key).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.delete_blob(key).await,
#[cfg(feature = "enterprise")]
BlobBackend::Composite(store) => store.delete_blob(key).await,
}

View file

@ -46,6 +46,9 @@ use backend::elastic::ElasticSearchStore;
#[cfg(feature = "redis")]
use backend::redis::RedisStore;
#[cfg(feature = "azure")]
use backend::azure::AzureStore;
pub trait Deserialize: Sized + Sync + Send {
fn deserialize(bytes: &[u8]) -> trc::Result<Self>;
}
@ -208,6 +211,8 @@ pub enum BlobBackend {
Fs(Arc<FsStore>),
#[cfg(feature = "s3")]
S3(Arc<S3Store>),
#[cfg(feature = "azure")]
Azure(Arc<AzureStore>),
#[cfg(feature = "enterprise")]
Composite(Arc<backend::composite::distributed_blob::DistributedBlob>),
}
@ -288,6 +293,16 @@ impl From<S3Store> for BlobStore {
}
}
#[cfg(feature = "azure")]
impl From<AzureStore> for BlobStore {
fn from(store: AzureStore) -> Self {
BlobStore {
backend: BlobBackend::Azure(Arc::new(store)),
compression: CompressionAlgo::None,
}
}
}
#[cfg(feature = "elastic")]
impl From<ElasticSearchStore> for FtsStore {
fn from(store: ElasticSearchStore) -> Self {

View file

@ -1540,6 +1540,7 @@ impl StoreEvent {
StoreEvent::ElasticsearchError => "ElasticSearch error",
StoreEvent::RedisError => "Redis error",
StoreEvent::S3Error => "S3 error",
StoreEvent::AzureError => "Azure error",
StoreEvent::FilesystemError => "Filesystem error",
StoreEvent::PoolError => "Connection pool error",
StoreEvent::DataCorruption => "Data corruption detected",
@ -1574,6 +1575,7 @@ impl StoreEvent {
StoreEvent::ElasticsearchError => "An ElasticSearch error occurred",
StoreEvent::RedisError => "A Redis error occurred",
StoreEvent::S3Error => "An S3 error occurred",
StoreEvent::AzureError => "An Azure error occurred",
StoreEvent::FilesystemError => "A filesystem error occurred",
StoreEvent::PoolError => "A connection pool error occurred",
StoreEvent::DataCorruption => "Data corruption was detected",

View file

@ -31,6 +31,7 @@ impl EventType {
| StoreEvent::ElasticsearchError
| StoreEvent::RedisError
| StoreEvent::S3Error
| StoreEvent::AzureError
| StoreEvent::FilesystemError
| StoreEvent::PoolError
| StoreEvent::DataCorruption

View file

@ -307,6 +307,7 @@ impl StoreEvent {
Self::ElasticsearchError => "ElasticSearch error",
Self::RedisError => "Redis error",
Self::S3Error => "S3 error",
Self::AzureError => "Azure error",
Self::FilesystemError => "Filesystem error",
Self::PoolError => "Connection pool error",
Self::DataCorruption => "Data corruption",

View file

@ -440,6 +440,7 @@ impl EventType {
| StoreEvent::ElasticsearchError
| StoreEvent::RedisError
| StoreEvent::S3Error
| StoreEvent::AzureError
| StoreEvent::FilesystemError
| StoreEvent::PoolError
| StoreEvent::DataCorruption

View file

@ -828,6 +828,7 @@ pub enum StoreEvent {
ElasticsearchError,
RedisError,
S3Error,
AzureError,
FilesystemError,
PoolError,
DataCorruption,

View file

@ -864,6 +864,7 @@ impl EventType {
EventType::Ai(AiEvent::LlmResponse) => 556,
EventType::Ai(AiEvent::ApiError) => 557,
EventType::Security(SecurityEvent::ScanBan) => 558,
EventType::Store(StoreEvent::AzureError) => 559,
}
}
@ -1468,6 +1469,7 @@ impl EventType {
556 => Some(EventType::Ai(AiEvent::LlmResponse)),
557 => Some(EventType::Ai(AiEvent::ApiError)),
558 => Some(EventType::Security(SecurityEvent::ScanBan)),
559 => Some(EventType::Store(StoreEvent::AzureError)),
_ => None,
}
}

View file

@ -5,7 +5,7 @@ edition = "2021"
resolver = "2"
[features]
default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "foundationdb"]
default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "azure", "foundationdb"]
#default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "foundationdb"]
sqlite = ["store/sqlite"]
foundationdb = ["store/foundation", "common/foundation"]
@ -15,6 +15,7 @@ rocks = ["store/rocks"]
elastic = ["store/elastic"]
s3 = ["store/s3"]
redis = ["store/redis"]
azure = ["store/azure"]
[dev-dependencies]
store = { path = "../crates/store", features = ["test_mode", "enterprise"] }