From 1522e296a2cb81892718d4f778c4fa89c7776b41 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Mon, 30 Sep 2024 13:03:37 +0200 Subject: [PATCH] feat: s3 persister (#542) Co-authored-by: Christopher Kolstad --- Cargo.lock | 942 +++++++++++++++++++++++++++++--- server/Cargo.toml | 9 +- server/src/builder.rs | 13 + server/src/cli.rs | 16 +- server/src/persistence/mod.rs | 1 + server/src/persistence/redis.rs | 1 - server/src/persistence/s3.rs | 177 ++++++ server/tests/redis_test.rs | 12 +- server/tests/s3_tests.rs | 83 +++ 9 files changed, 1172 insertions(+), 82 deletions(-) create mode 100644 server/src/persistence/s3.rs create mode 100644 server/tests/s3_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 54616262..a0e498aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ "pin-project-lite", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "tracing", ] @@ -315,6 +315,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -448,6 +454,381 @@ dependencies = [ "tokio", ] +[[package]] +name = "aws-config" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848d7b9b605720989929279fa644ce8f244d0ce3146fcca5b70e4eb7b3c020fc" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.11", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09fd4b5c7ed75f52b913b4f3ff0501dae7f8cb9125f6d45db4553980cbc0528" +dependencies = [ + "ahash", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.11", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a9d27ed1c12b1140c47daf1bc541606c43fdafd918c4797d520db0043ceef2" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44514a6ca967686cde1e2a1b81df6ef1883d0e3e570da8d8bc5c491dcb6fc29b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7a4d279762a35b9df97209f6808b95d4fe78547fe2316b4d200a0283960c5a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.11", + "http 1.1.0", + "once_cell", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.11", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.11", + "http-body 0.4.6", + "http-body 1.0.0", + "httparse", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.11", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -463,6 +844,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.21.7" @@ -475,6 +862,22 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -511,16 +914,16 @@ dependencies = [ "home", "http 1.1.0", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-named-pipe", - "hyper-rustls", + "hyper-rustls 0.27.2", "hyper-util", "hyperlocal", "log", "pin-project-lite", - "rustls", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.23.13", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", "serde_derive", @@ -579,6 +982,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bytestring" version = "1.3.1" @@ -700,6 +1113,12 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_fn" version = "0.4.9" @@ -783,6 +1202,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -798,6 +1226,28 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -905,6 +1355,16 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -978,27 +1438,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", + "subtle", ] [[package]] @@ -1018,9 +1458,21 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] @@ -1029,6 +1481,26 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1077,12 +1549,45 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "flate2" version = "1.0.28" @@ -1260,6 +1765,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -1290,6 +1806,10 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "heck" @@ -1309,6 +1829,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -1340,6 +1869,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1359,7 +1899,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -1381,6 +1921,30 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.4.1" @@ -1391,7 +1955,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -1408,7 +1972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper", + "hyper 1.4.1", "hyper-util", "pin-project-lite", "tokio", @@ -1416,6 +1980,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper 0.14.30", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.2" @@ -1424,12 +2004,12 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.4.1", "hyper-util", - "rustls", + "rustls 0.23.13", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", "webpki-roots", ] @@ -1442,7 +2022,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-util", "native-tls", "tokio", @@ -1460,8 +2040,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.4.1", "pin-project-lite", "socket2", "tokio", @@ -1478,7 +2058,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-util", "pin-project-lite", "tokio", @@ -1663,6 +2243,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.4.2", "libc", + "redox_syscall 0.5.6", ] [[package]] @@ -1716,6 +2297,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown 0.14.3", +] + [[package]] name = "maplit" version = "1.0.2" @@ -1731,6 +2321,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2007,10 +2607,10 @@ dependencies = [ ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "outref" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "overload" @@ -2018,6 +2618,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2036,7 +2647,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -2155,6 +2766,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.29" @@ -2284,7 +2905,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.13", "thiserror", "tokio", "tracing", @@ -2300,7 +2921,7 @@ dependencies = [ "rand", "ring", "rustc-hash", - "rustls", + "rustls 0.23.13", "slab", "thiserror", "tinyvec", @@ -2376,19 +2997,28 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand", - "rustls", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.23.13", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.3", "rustls-pki-types", "ryu", "sha1_smol", "socket2", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "url", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2399,14 +3029,12 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.5" +name = "redox_syscall" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "bitflags 2.4.2", ] [[package]] @@ -2471,10 +3099,10 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.4.1", + "hyper-rustls 0.27.2", "hyper-tls", "hyper-util", "ipnet", @@ -2486,8 +3114,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-pemfile", + "rustls 0.23.13", + "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", "serde_json", @@ -2495,7 +3123,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", "url", "wasm-bindgen", @@ -2505,6 +3133,17 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + [[package]] name = "ring" version = "0.17.7" @@ -2587,6 +3226,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.13" @@ -2597,11 +3248,23 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + [[package]] name = "rustls-native-certs" version = "0.7.0" @@ -2609,12 +3272,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.3", "rustls-pki-types", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.1.3" @@ -2631,6 +3303,16 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -2672,6 +3354,30 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -2859,6 +3565,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -2890,6 +3606,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "str-buf" version = "3.0.3" @@ -3015,17 +3741,17 @@ dependencies = [ [[package]] name = "testcontainers" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef8374cea2c164699681ecc39316c3e1d953831a7a5721e36c7736d974e15fa" +checksum = "5f40cc2bd72e17f328faf8ca7687fe337e61bccd8acf9674fa78dd3792b045e1" dependencies = [ "async-trait", "bollard", "bollard-stubs", "bytes", - "dirs", "docker_credential", "either", + "etcetera", "futures", "log", "memchr", @@ -3037,15 +3763,16 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "tokio-tar", "tokio-util", "url", ] [[package]] name = "testcontainers-modules" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359d9a225791e1b9f60aab01f9ae9471898b9b9904b5db192104a71e96785079" +checksum = "7aef7d623e245645e421c3a6d40d92e4991524b8496f39a0c7ed7994653dbcbc" dependencies = [ "testcontainers", ] @@ -3168,13 +3895,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -3190,6 +3927,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -3457,6 +4209,8 @@ dependencies = [ "ahash", "anyhow", "async-trait", + "aws-config", + "aws-sdk-s3", "chrono", "cidr", "clap", @@ -3479,8 +4233,8 @@ dependencies = [ "rand", "redis", "reqwest", - "rustls", - "rustls-pemfile", + "rustls 0.23.13", + "rustls-pemfile 2.1.3", "rustls-pki-types", "semver", "serde", @@ -3556,6 +4310,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.1" @@ -3605,6 +4365,12 @@ dependencies = [ "zip", ] +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + [[package]] name = "valuable" version = "0.1.0" @@ -3623,6 +4389,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.4.0" @@ -3831,6 +4603,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3961,6 +4742,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xxhash-rust" version = "0.8.12" diff --git a/server/Cargo.toml b/server/Cargo.toml index 8697d403..4838d68d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -32,6 +32,8 @@ actix-web = { version = "4.9.0", features = ["rustls-0_23", "compress-zstd"] } ahash = "0.8.11" anyhow = "1.0.89" async-trait = "0.1.82" +aws-config = { version = "1.5.6", features = ["behavior-version-latest"] } +aws-sdk-s3 = { version = "1.51.0", features = ["behavior-version-latest"] } chrono = { version = "0.4.38", features = ["serde"] } cidr = "0.2.3" clap = { version = "4.5.16", features = ["derive", "env"] } @@ -98,8 +100,11 @@ env_logger = "0.11.5" maplit = "1.0.2" rand = "0.8.5" test-case = "3.3.1" -testcontainers = "0.22.0" -testcontainers-modules = { version = "0.10.0", features = ["redis"] } +testcontainers = "0.23.1" +testcontainers-modules = { version = "0.11.1", features = [ + "redis", + "localstack", +] } tracing-test = "0.2.5" [build-dependencies] diff --git a/server/src/builder.rs b/server/src/builder.rs index 31181556..ea8b1759 100644 --- a/server/src/builder.rs +++ b/server/src/builder.rs @@ -15,6 +15,7 @@ use crate::http::unleash_client::new_reqwest_client; use crate::offline::offline_hotload::{load_bootstrap, load_offline_engine_cache}; use crate::persistence::file::FilePersister; use crate::persistence::redis::RedisPersister; +use crate::persistence::s3::S3Persister; use crate::persistence::EdgePersistence; use crate::{ auth::token_validator::TokenValidator, @@ -199,6 +200,17 @@ async fn get_data_source(args: &EdgeArgs) -> Option> { return Some(Arc::new(redis_persister)); } + if let Some(s3_args) = args.s3.clone() { + let s3_persister = S3Persister::new_from_env( + &s3_args + .s3_bucket_name + .clone() + .expect("Clap is confused, there's no bucket name"), + ) + .await; + return Some(Arc::new(s3_persister)); + } + if let Some(backup_folder) = args.backup_folder.clone() { debug!("Configuring file persistence {backup_folder:?}"); let backup_client = FilePersister::new(&backup_folder); @@ -335,6 +347,7 @@ mod tests { dynamic: false, tokens: vec![], redis: None, + s3: None, client_identity: Default::default(), skip_ssl_verification: false, upstream_request_timeout: Default::default(), diff --git a/server/src/cli.rs b/server/src/cli.rs index 8689c2b3..5bd8b8fd 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -44,6 +44,14 @@ impl Display for RedisScheme { } } } + +#[derive(Args, Debug, Clone)] +pub struct S3Args { + /// Bucket name to use for storing feature and token data + #[clap(long, env)] + pub s3_bucket_name: Option, +} + #[derive(Copy, Debug, Clone, Eq, PartialEq, PartialOrd, Ord, ValueEnum)] pub enum RedisMode { Single, @@ -131,7 +139,7 @@ pub struct ClientIdentity { #[derive(Args, Debug, Clone)] #[command(group( ArgGroup::new("data-provider") - .args(["redis_url", "backup_folder"]), + .args(["redis_url", "backup_folder", "s3_bucket_name"]), ))] pub struct EdgeArgs { /// Where is your upstream URL. Remember, this is the URL to your instance, without any trailing /api suffix @@ -180,10 +188,14 @@ pub struct EdgeArgs { #[clap(long, env, default_value_t = 5)] pub upstream_socket_timeout: i64, - /// A URL pointing to a running Redis instance. Edge will use this instance to persist feature and token data and read this back after restart. Mutually exclusive with the --backup-folder option + /// A URL pointing to a running Redis instance. Edge will use this instance to persist feature and token data and read this back after restart. Mutually exclusive with the --backup-folder and --s3-bucket options #[clap(flatten)] pub redis: Option, + /// Configuration for S3 storage. Edge will use this instance to persist feature and token data and read this back after restart. Mutually exclusive with the --redis-url and --backup-folder options + #[clap(flatten)] + pub s3: Option, + /// Token header to use for both edge authorization and communication with the upstream server. #[clap(long, env, global = true, default_value = "Authorization")] pub token_header: TokenHeader, diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index bd091b5b..122f3d95 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -9,6 +9,7 @@ use crate::types::{EdgeResult, EdgeToken, TokenValidationStatus}; pub mod file; pub mod redis; +pub mod s3; #[async_trait] pub trait EdgePersistence: Send + Sync { diff --git a/server/src/persistence/redis.rs b/server/src/persistence/redis.rs index fa14db9b..15375410 100644 --- a/server/src/persistence/redis.rs +++ b/server/src/persistence/redis.rs @@ -17,7 +17,6 @@ use super::EdgePersistence; pub const FEATURES_KEY: &str = "unleash-features"; pub const TOKENS_KEY: &str = "unleash-tokens"; -pub const REFRESH_TARGETS_KEY: &str = "unleash-refresh-targets"; impl From for EdgeError { fn from(err: RedisError) -> Self { diff --git a/server/src/persistence/s3.rs b/server/src/persistence/s3.rs new file mode 100644 index 00000000..8e85f90f --- /dev/null +++ b/server/src/persistence/s3.rs @@ -0,0 +1,177 @@ +use std::collections::HashMap; + +use async_trait::async_trait; +use unleash_types::client_features::ClientFeatures; + +use super::EdgePersistence; +use crate::{ + error::EdgeError, + types::{EdgeResult, EdgeToken}, +}; +use aws_sdk_s3::{ + self as s3, + error::SdkError, + operation::{get_object::GetObjectError, put_object::PutObjectError}, + primitives::{ByteStream, SdkBody}, +}; + +pub const FEATURES_KEY: &str = "/unleash-features.json"; +pub const TOKENS_KEY: &str = "/unleash-tokens.json"; + +pub struct S3Persister { + client: s3::Client, + bucket: String, +} + +impl S3Persister { + pub fn new_with_config(bucket_name: &str, config: s3::config::Config) -> Self { + let client = s3::Client::from_conf(config); + Self { + client, + bucket: bucket_name.to_string(), + } + } + pub async fn new_from_env(bucket_name: &str) -> Self { + let shared_config = aws_config::load_from_env().await; + let client = s3::Client::new(&shared_config); + Self { + client, + bucket: bucket_name.to_string(), + } + } +} + +impl From> for EdgeError { + fn from(err: SdkError) -> Self { + EdgeError::PersistenceError(format!("failed to get object {}", err)) + } +} + +impl From> for EdgeError { + fn from(err: SdkError) -> Self { + EdgeError::PersistenceError(format!("failed to put object {}", err)) + } +} + +impl S3Persister { + async fn create_bucket_if_not_exists(&self) -> EdgeResult<()> { + match self + .client + .create_bucket() + .bucket(&self.bucket) + .send() + .await + { + Ok(_) => Ok(()), + Err(err) => { + if err.to_string().contains("BucketAlreadyOwnedByYou") + || err.to_string().contains("BucketAlreadyExists") + { + Ok(()) + } else { + Err(EdgeError::PersistenceError(format!( + "Failed to create bucket: {}", + err + ))) + } + } + } + } +} + +#[async_trait] +impl EdgePersistence for S3Persister { + async fn load_tokens(&self) -> EdgeResult> { + let response = self + .client + .get_object() + .bucket(self.bucket.clone()) + .key(TOKENS_KEY) + .response_content_type("application/json") + .send() + .await?; + let data = response.body.collect().await.expect("Failed data"); + serde_json::from_slice(&data.to_vec()) + .map_err(|_| EdgeError::PersistenceError("Failed to deserialize tokens".to_string())) + } + + async fn save_tokens(&self, tokens: Vec) -> EdgeResult<()> { + self.create_bucket_if_not_exists().await?; + let body_data = serde_json::to_vec(&tokens) + .map_err(|_| EdgeError::PersistenceError("Failed to serialize tokens".to_string())) + .map(SdkBody::from)?; + let byte_stream = aws_sdk_s3::primitives::ByteStream::new(body_data); + self.client + .put_object() + .bucket(self.bucket.clone()) + .key(TOKENS_KEY) + .body(byte_stream) + .send() + .await + .map(|_| ()) + .map_err(|err| { + dbg!(err); + EdgeError::PersistenceError("Failed to save tokens".to_string()) + }) + } + + async fn load_features(&self) -> EdgeResult> { + let query = self + .client + .get_object() + .bucket(self.bucket.clone()) + .key(FEATURES_KEY) + .response_content_type("application/json") + .send() + .await + .map_err(|err| { + if err.to_string().contains("NoSuchKey") { + return EdgeError::PersistenceError("No features found".to_string()); + } + dbg!(err); + EdgeError::PersistenceError("Failed to load features".to_string()) + }); + match query { + Ok(response) => { + let data = response.body.collect().await.expect("Failed data"); + let deser: Vec<(String, ClientFeatures)> = serde_json::from_slice(&data.to_vec()) + .map_err(|_| { + EdgeError::PersistenceError("Failed to deserialize features".to_string()) + })?; + Ok(deser + .iter() + .cloned() + .collect::>()) + } + Err(e) => { + eprintln!("Err Arg, failed to read features"); + dbg!(e); + Ok(HashMap::new()) + } + } + } + + async fn save_features(&self, features: Vec<(String, ClientFeatures)>) -> EdgeResult<()> { + self.create_bucket_if_not_exists().await?; + let body_data = serde_json::to_vec(&features) + .map_err(|_| EdgeError::PersistenceError("Failed to serialize features".to_string()))?; + let byte_stream = ByteStream::new(SdkBody::from(body_data)); + match self + .client + .put_object() + .bucket(self.bucket.clone()) + .key(FEATURES_KEY) + .body(byte_stream) + .send() + .await + { + Ok(_) => Ok(()), + Err(s3_err) => { + dbg!(s3_err); + Err(EdgeError::PersistenceError( + "Failed to save features".to_string(), + )) + } + } + } +} diff --git a/server/tests/redis_test.rs b/server/tests/redis_test.rs index 24d99b55..8b68f2aa 100644 --- a/server/tests/redis_test.rs +++ b/server/tests/redis_test.rs @@ -1,11 +1,10 @@ use std::{str::FromStr, time::Duration}; use redis::Client; -use testcontainers::runners::AsyncRunner; -use testcontainers::ContainerAsync; -use testcontainers_modules::redis::Redis; +use testcontainers_modules::redis::RedisStack; use unleash_types::client_features::{ClientFeature, ClientFeatures}; +use testcontainers::{runners::AsyncRunner, ContainerAsync}; use unleash_edge::{ persistence::{redis::RedisPersister, EdgePersistence}, types::{EdgeToken, TokenType}, @@ -13,8 +12,11 @@ use unleash_edge::{ const TEST_TIMEOUT: Duration = std::time::Duration::from_millis(1000); -async fn setup_redis() -> (Client, String, ContainerAsync) { - let node = Redis.start().await.expect("Failed to start redis"); +async fn setup_redis() -> (Client, String, ContainerAsync) { + let node = RedisStack::default() + .start() + .await + .expect("Failed to start redis"); let host_port = node .get_host_port_ipv4(6379) .await diff --git a/server/tests/s3_tests.rs b/server/tests/s3_tests.rs new file mode 100644 index 00000000..f5a1e15f --- /dev/null +++ b/server/tests/s3_tests.rs @@ -0,0 +1,83 @@ +#[cfg(test)] +mod s3_tests { + + use std::collections::HashMap; + use std::str::FromStr; + + use aws_config::Region; + use aws_sdk_s3 as s3; + use aws_sdk_s3::config::Credentials; + use aws_sdk_s3::config::SharedCredentialsProvider; + use testcontainers::{runners::AsyncRunner, ImageExt}; + use testcontainers_modules::localstack::LocalStack; + use unleash_edge::persistence::s3::S3Persister; + use unleash_edge::persistence::EdgePersistence; + use unleash_edge::types::EdgeToken; + use unleash_types::client_features::ClientFeature; + use unleash_types::client_features::ClientFeatures; + + #[tokio::test] + async fn test_s3_persister() { + let localstack = LocalStack::default() + .with_env_var("SERVICES", "s3") + .start() + .await + .expect("Failed to start localstack"); + + let bucket_name = "test-bucket"; + let local_stack_ip = localstack.get_host().await.expect("Could not get host"); + let local_stack_port = localstack + .get_host_port_ipv4(4566) + .await + .expect("Could not get port"); + + let client_features_one = ClientFeatures { + version: 2, + features: vec![ + ClientFeature { + name: "feature1".into(), + ..ClientFeature::default() + }, + ClientFeature { + name: "feature2".into(), + ..ClientFeature::default() + }, + ], + segments: None, + query: None, + }; + let config = s3::config::Config::builder() + .region(Region::new("us-east-1")) + .endpoint_url(format!("http://{}:{}", local_stack_ip, local_stack_port)) + .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .force_path_style(true) + .build(); + + //hopefully we don't care, this should just work with localstack + let persister = S3Persister::new_with_config(bucket_name, config); + + let tokens = vec![EdgeToken::from_str("eg:development.secret321").unwrap()]; + persister.save_tokens(tokens.clone()).await.unwrap(); + + let loaded_tokens = persister + .load_tokens() + .await + .expect("Failed to load tokens"); + assert_eq!(tokens, loaded_tokens); + + let features = vec![("test".to_string(), client_features_one.clone())]; + persister + .save_features(features.clone()) + .await + .expect("Failed to save features"); + + let loaded_features = persister + .load_features() + .await + .expect("Failed to load features"); + assert_eq!( + features.into_iter().collect::>(), + loaded_features + ); + } +}