diff --git a/Cargo.lock b/Cargo.lock index 489801c..fca6cc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.19" @@ -201,18 +216,91 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bollard" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06" +dependencies = [ + "base64", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.48.3-rc.28.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368" +dependencies = [ + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "clap" version = "4.5.40" @@ -259,12 +347,45 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -283,6 +404,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -438,6 +568,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "1.3.1" @@ -505,6 +641,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -539,6 +690,152 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -547,6 +844,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -557,6 +855,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", + "serde", ] [[package]] @@ -591,6 +890,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -603,6 +912,12 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" version = "0.4.13" @@ -672,8 +987,10 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "bollard", "bytes", "clap", + "futures-util", "norun-grpc-interface", "notmad", "pretty_assertions", @@ -726,6 +1043,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -814,6 +1146,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -957,6 +1304,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.1" @@ -1013,6 +1380,36 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1039,6 +1436,29 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -1048,6 +1468,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "time", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1057,6 +1508,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -1088,6 +1545,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -1111,6 +1574,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "2.0.12" @@ -1140,6 +1614,47 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.46.1" @@ -1386,6 +1901,23 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1422,6 +1954,64 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1444,6 +2034,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1544,12 +2193,42 @@ dependencies = [ "bitflags", ] +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -1569,3 +2248,57 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/README.md b/README.md index 93b5f5f..7898680 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # Norun +## Server + +Many servers + ```bash -norun subscribe apps +norun subscribe apps ``` ```bash @@ -18,6 +22,7 @@ name = "hello-world" [container] image = "kasperhermansen/hello-world" version = "latest" # default +replicas = 3 [expose] port = 8080 diff --git a/crates/norun-grpc-interface/src/grpc/norun.v1.rs b/crates/norun-grpc-interface/src/grpc/norun.v1.rs index 8f5284c..da22b28 100644 --- a/crates/norun-grpc-interface/src/grpc/norun.v1.rs +++ b/crates/norun-grpc-interface/src/grpc/norun.v1.rs @@ -12,6 +12,24 @@ pub struct PublishResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetTopicRequest { + #[prost(string, tag="1")] + pub topic: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetTopicResponse { + #[prost(message, optional, tag="1")] + pub projects: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Projects { + #[prost(message, repeated, tag="1")] + pub projects: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Project { #[prost(string, tag="1")] pub name: ::prost::alloc::string::String, diff --git a/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs b/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs index 5b21359..8044df2 100644 --- a/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs +++ b/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs @@ -4,6 +4,7 @@ pub mod registry_service_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; use tonic::codegen::http::Uri; + /// #[derive(Debug, Clone)] pub struct RegistryServiceClient { inner: tonic::client::Grpc, @@ -84,6 +85,7 @@ pub mod registry_service_client { self.inner = self.inner.max_encoding_message_size(limit); self } + /// pub async fn publish( &mut self, request: impl tonic::IntoRequest, @@ -109,6 +111,32 @@ pub mod registry_service_client { .insert(GrpcMethod::new("norun.v1.RegistryService", "Publish")); self.inner.unary(req, path, codec).await } + /// + pub async fn get_topic( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/norun.v1.RegistryService/GetTopic", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("norun.v1.RegistryService", "GetTopic")); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -118,11 +146,21 @@ pub mod registry_service_server { /// Generated trait containing gRPC methods that should be implemented for use with RegistryServiceServer. #[async_trait] pub trait RegistryService: Send + Sync + 'static { + /// async fn publish( &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// + async fn get_topic( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } + /// #[derive(Debug)] pub struct RegistryServiceServer { inner: _Inner, @@ -248,6 +286,52 @@ pub mod registry_service_server { }; Box::pin(fut) } + "/norun.v1.RegistryService/GetTopic" => { + #[allow(non_camel_case_types)] + struct GetTopicSvc(pub Arc); + impl< + T: RegistryService, + > tonic::server::UnaryService + for GetTopicSvc { + type Response = super::GetTopicResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_topic(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetTopicSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/norun/Cargo.toml b/crates/norun/Cargo.toml index 8800af9..13c0bfe 100644 --- a/crates/norun/Cargo.toml +++ b/crates/norun/Cargo.toml @@ -21,6 +21,8 @@ tonic = { workspace = true } tokio-util = "0.7.15" async-trait = "0.1.88" notmad = "0.7.2" +bollard = "0.19.1" +futures-util = "0.3.31" [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/crates/norun/src/cli.rs b/crates/norun/src/cli.rs index ee65dd8..b89c494 100644 --- a/crates/norun/src/cli.rs +++ b/crates/norun/src/cli.rs @@ -1,12 +1,13 @@ use clap::{Parser, Subcommand}; use crate::{ - cli::{publish::PublishCommand, serve::ServeCommand}, + cli::{publish::PublishCommand, serve::ServeCommand, subscribe::SubscribeCommand}, state::ClientState, }; mod publish; mod serve; +mod subscribe; #[derive(Parser)] #[command(author, version, about)] @@ -24,6 +25,7 @@ struct Cli { #[derive(Subcommand)] enum CliSubcommands { + Subscribe(SubscribeCommand), Publish(PublishCommand), Serve(ServeCommand), } @@ -36,5 +38,6 @@ pub async fn execute() -> anyhow::Result<()> { match cmd.subcommands { CliSubcommands::Publish(cmd) => cmd.execute(&state).await, CliSubcommands::Serve(cmd) => cmd.execute().await, + CliSubcommands::Subscribe(cmd) => cmd.execute(&state).await, } } diff --git a/crates/norun/src/cli/publish.rs b/crates/norun/src/cli/publish.rs index 281f4c9..5719709 100644 --- a/crates/norun/src/cli/publish.rs +++ b/crates/norun/src/cli/publish.rs @@ -4,9 +4,6 @@ use crate::{grpc_client::GrpcClientState, models::ProjectTag, project_file, stat #[derive(clap::Parser, Debug)] pub struct PublishCommand { - #[arg(value_parser = clap::value_parser!(ProjectTag))] - project_tag: ProjectTag, - #[arg(long = "project-path", default_value = ".")] project_path: PathBuf, } diff --git a/crates/norun/src/cli/subscribe.rs b/crates/norun/src/cli/subscribe.rs new file mode 100644 index 0000000..c986dbb --- /dev/null +++ b/crates/norun/src/cli/subscribe.rs @@ -0,0 +1,31 @@ +use crate::{ + container_runtime::ContainerRuntimeState, grpc_client::GrpcClientState, state::ClientState, +}; + +#[derive(clap::Parser)] +pub struct SubscribeCommand { + #[arg(long)] + topic: String, +} + +impl SubscribeCommand { + pub async fn execute(&self, state: &ClientState) -> anyhow::Result<()> { + let projects = state.grpc_client().subscribe(&self.topic).await?; + + let runtime = state.container_runtime(); + + println!("printing found projects (len={})", projects.projects.len()); + for project in projects.projects { + println!("project: {project:?}"); + + runtime + .ensure_running( + &project.name, + &format!("{}:{}", project.image, project.version), + ) + .await?; + } + + Ok(()) + } +} diff --git a/crates/norun/src/container_runtime.rs b/crates/norun/src/container_runtime.rs new file mode 100644 index 0000000..8d72d58 --- /dev/null +++ b/crates/norun/src/container_runtime.rs @@ -0,0 +1,85 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use bollard::{ + Docker, + image::CreateImageOptions, + query_parameters::{ + CreateContainerOptionsBuilder, CreateImageOptionsBuilder, ListContainersOptionsBuilder, + StartContainerOptions, + }, + secret::ContainerCreateBody, +}; +use futures_util::TryStreamExt; + +use crate::state::ClientState; + +#[derive(Clone)] +pub struct ContainerRuntime { + client: Docker, +} + +impl ContainerRuntime { + #[tracing::instrument(skip(self), level = "trace")] + pub async fn ensure_running(&self, name: &str, image: &str) -> anyhow::Result<()> { + tracing::debug!("ensuring that image is running"); + + let containers = self + .client + .list_containers(Some( + ListContainersOptionsBuilder::default() + .all(true) + .filters(&HashMap::from([( + "name".to_string(), + vec![name.to_string()], + )])) + .build(), + )) + .await?; + if !containers.is_empty() { + // Reconcile difference + return Ok(()); + } + + let _ = self + .client + .create_image( + Some(CreateImageOptionsBuilder::new().from_image(image).build()), + None, + None, + ) + .try_collect::>() + .await?; + + self.client + .create_container( + Some(CreateContainerOptionsBuilder::new().name(name).build()), + ContainerCreateBody { + image: Some(image.into()), + ..Default::default() + }, + ) + .await?; + + self.client + .start_container(name, None::) + .await?; + + Ok(()) + } +} + +pub trait ContainerRuntimeState { + fn container_runtime(&self) -> ContainerRuntime; +} + +impl ContainerRuntimeState for ClientState { + fn container_runtime(&self) -> ContainerRuntime { + static CLIENT: LazyLock = LazyLock::new(|| { + Docker::connect_with_defaults().expect("to be able to connect to a docker daemon") + }); + + ContainerRuntime { + client: CLIENT.clone(), + } + } +} diff --git a/crates/norun/src/grpc_client.rs b/crates/norun/src/grpc_client.rs index 453098e..cc9c641 100644 --- a/crates/norun/src/grpc_client.rs +++ b/crates/norun/src/grpc_client.rs @@ -1,4 +1,6 @@ -use norun_grpc_interface::{PublishRequest, registry_service_client::RegistryServiceClient}; +use norun_grpc_interface::{ + GetTopicRequest, Projects, PublishRequest, registry_service_client::RegistryServiceClient, +}; use tokio::sync::OnceCell; use tonic::transport::Channel; @@ -26,6 +28,23 @@ impl GrpcClient { Ok(()) } + pub async fn subscribe(&self, topic: &str) -> anyhow::Result { + tracing::trace!("calling subscribe via. grpc on registry"); + + let mut registry_client = self.get_registry_client().await?; + + let res = registry_client + .get_topic(GetTopicRequest { + topic: topic.to_string(), + }) + .await?; + + let res = res.into_inner(); + + res.projects + .ok_or_else(|| anyhow::anyhow!("failed to get projects from server")) + } + async fn get_registry_client(&self) -> anyhow::Result> { let client = self .registry_client diff --git a/crates/norun/src/grpc_server/registry.rs b/crates/norun/src/grpc_server/registry.rs index 9d142a0..bc48224 100644 --- a/crates/norun/src/grpc_server/registry.rs +++ b/crates/norun/src/grpc_server/registry.rs @@ -1,6 +1,9 @@ use norun_grpc_interface::{registry_service_server::RegistryService, *}; -use crate::{server::services::registry::RegistryServiceState, state::ServerState}; +use crate::{ + server::services::registry::{self, RegistryServiceState}, + state::ServerState, +}; pub struct GrpcRegistryService { pub state: ServerState, @@ -8,7 +11,7 @@ pub struct GrpcRegistryService { #[async_trait::async_trait] impl RegistryService for GrpcRegistryService { - #[tracing::instrument(skip(self), level = "debug")] + #[tracing::instrument(skip(self), level = "trace")] async fn publish( &self, request: tonic::Request, @@ -30,4 +33,34 @@ impl RegistryService for GrpcRegistryService { Ok(tonic::Response::new(PublishResponse {})) } + + #[tracing::instrument(skip(self), level = "trace")] + async fn get_topic( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + tracing::debug!("subscribe called"); + + let req = request.into_inner(); + + let projects = self + .state + .registry_service() + .get_topic(&req.topic) + .await + .inspect_err(|e| tracing::warn!("failed to subscribe on topic: {}", e)) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + + Ok(tonic::Response::new(GetTopicResponse { + projects: Some(projects.into()), + })) + } +} + +impl From for Projects { + fn from(value: registry::Projects) -> Self { + Self { + projects: value.projects.into_iter().collect(), + } + } } diff --git a/crates/norun/src/main.rs b/crates/norun/src/main.rs index a737a5f..233653c 100644 --- a/crates/norun/src/main.rs +++ b/crates/norun/src/main.rs @@ -10,6 +10,8 @@ mod server; mod grpc_client; mod grpc_server; +mod container_runtime; + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() diff --git a/crates/norun/src/server/services/registry.rs b/crates/norun/src/server/services/registry.rs index 720644d..b7ef1ad 100644 --- a/crates/norun/src/server/services/registry.rs +++ b/crates/norun/src/server/services/registry.rs @@ -1,7 +1,4 @@ -use std::{ - collections::BTreeMap, - sync::{Arc, LazyLock}, -}; +use std::sync::{Arc, LazyLock}; use norun_grpc_interface::Project; use tokio::sync::Mutex; @@ -25,6 +22,25 @@ impl RegistryService { Ok(()) } + + pub async fn get_topic(&self, topic: &str) -> anyhow::Result { + tracing::debug!("get projects for topic"); + + let projects = { + let store = self.store.lock().await; + store + .iter() + .filter(|i| i.name == topic) + .cloned() + .collect::>() + }; + + Ok(Projects { projects }) + } +} + +pub struct Projects { + pub projects: Vec, } pub trait RegistryServiceState { diff --git a/examples/hello-world/norun.toml b/examples/hello-world/norun.toml index fc6e67d..113d934 100644 --- a/examples/hello-world/norun.toml +++ b/examples/hello-world/norun.toml @@ -2,7 +2,7 @@ name = "hello-world" [container] -image = "kasperhermansen/hello-world" +image = "library/hello-world" version = "latest" [expose] diff --git a/examples/nginx/norun.toml b/examples/nginx/norun.toml new file mode 100644 index 0000000..50ed68e --- /dev/null +++ b/examples/nginx/norun.toml @@ -0,0 +1,9 @@ +[project] +name = "nginx" + +[container] +image = "library/nginx" +version = "latest" + +[expose] +port = 80 diff --git a/interface/proto/norun/v1/registry.proto b/interface/proto/norun/v1/registry.proto index b1a16db..5e3bded 100644 --- a/interface/proto/norun/v1/registry.proto +++ b/interface/proto/norun/v1/registry.proto @@ -2,15 +2,29 @@ syntax = "proto3"; package norun.v1; -service RegistryService { - rpc Publish(PublishRequest) returns (PublishResponse) {} -} message PublishRequest { Project project = 1; } message PublishResponse {} +message GetTopicRequest { + string topic = 1; +} +message GetTopicResponse { + Projects projects = 1; +} + + +service RegistryService { + rpc Publish(PublishRequest) returns (PublishResponse) {} + rpc GetTopic(GetTopicRequest) returns (GetTopicResponse) {} +} + +message Projects { + repeated Project projects = 1; +} + message Project { string name = 1;