diff --git a/Cargo.lock b/Cargo.lock index b255e79..489801c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,12 +82,98 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -103,6 +189,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.9.1" @@ -167,18 +259,306 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.10.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + [[package]] name = "io-uring" version = "0.7.8" @@ -196,6 +576,21 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "lazy_static" version = "1.5.0" @@ -233,12 +628,24 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -255,7 +662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -264,12 +671,51 @@ name = "norun" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "bytes", "clap", + "norun-grpc-interface", + "notmad", + "pretty_assertions", + "prost", + "prost-types", + "serde", "tokio", + "tokio-util", + "toml", + "tonic", "tracing", "tracing-subscriber", ] +[[package]] +name = "norun-grpc-interface" +version = "0.1.0" +dependencies = [ + "bytes", + "prost", + "prost-types", + "tokio-util", + "tonic", +] + +[[package]] +name = "notmad" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c167eaa1d71a48d701a5b95cccdd3ca5be0f372c8c085d74eaded314537aa0b" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "futures-util", + "rand 0.9.1", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -330,12 +776,63 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -345,6 +842,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -354,6 +883,71 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "redox_syscall" version = "0.5.13" @@ -413,12 +1007,47 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -476,6 +1105,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -516,6 +1171,147 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.10.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -578,6 +1374,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -596,12 +1398,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -705,3 +1525,47 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index a848b85..2f0a783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,12 @@ [workspace] resolver = "2" members = ["crates/*"] + +[workspace.dependencies] +norun = { path = "./crates/norun" } +norun-grpc-interface = { path = "./crates/norun-grpc-interface" } + +bytes = "1.10.1" +prost = "0.13.5" +prost-types = "0.13.5" +tonic = "0.12.1" diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..27ad1c4 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,10 @@ +version: v2 +managed: + enabled: true +plugins: +- remote: buf.build/community/neoeinstein-prost:v0.4.0 + out: ./crates/norun-grpc-interface/src/grpc/ +- remote: buf.build/community/neoeinstein-tonic:v0.4.0 + out: ./crates/norun-grpc-interface/src/grpc/ +inputs: + - directory: ./interface/proto diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..d9d8bff --- /dev/null +++ b/buf.yaml @@ -0,0 +1,4 @@ +version: v2 +modules: + - path: interface/proto + name: buf.build/noschemaplz/norun diff --git a/crates/norun-grpc-interface/Cargo.toml b/crates/norun-grpc-interface/Cargo.toml new file mode 100644 index 0000000..e0d18e9 --- /dev/null +++ b/crates/norun-grpc-interface/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "norun-grpc-interface" +version = "0.1.0" +edition = "2024" + +[dependencies] +bytes = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +tokio-util = "0.7.15" +tonic = { workspace = true } diff --git a/crates/norun-grpc-interface/src/grpc/norun.v1.rs b/crates/norun-grpc-interface/src/grpc/norun.v1.rs new file mode 100644 index 0000000..8f5284c --- /dev/null +++ b/crates/norun-grpc-interface/src/grpc/norun.v1.rs @@ -0,0 +1,26 @@ +// @generated +// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PublishRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct PublishResponse { +} +#[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, + #[prost(string, tag="2")] + pub image: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub version: ::prost::alloc::string::String, + #[prost(uint32, optional, tag="4")] + pub port: ::core::option::Option, +} +include!("norun.v1.tonic.rs"); +// @@protoc_insertion_point(module) \ No newline at end of file diff --git a/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs b/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs new file mode 100644 index 0000000..5b21359 --- /dev/null +++ b/crates/norun-grpc-interface/src/grpc/norun.v1.tonic.rs @@ -0,0 +1,291 @@ +// @generated +/// Generated client implementations. +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, + } + impl RegistryServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl RegistryServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> RegistryServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + RegistryServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn publish( + &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/Publish", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("norun.v1.RegistryService", "Publish")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod registry_service_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// 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>; + } + #[derive(Debug)] + pub struct RegistryServiceServer { + inner: _Inner, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + struct _Inner(Arc); + impl RegistryServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for RegistryServiceServer + where + T: RegistryService, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/norun.v1.RegistryService/Publish" => { + #[allow(non_camel_case_types)] + struct PublishSvc(pub Arc); + impl< + T: RegistryService, + > tonic::server::UnaryService + for PublishSvc { + type Response = super::PublishResponse; + 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 { + ::publish(&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 = PublishSvc(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( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for RegistryServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } + impl tonic::server::NamedService for RegistryServiceServer { + const NAME: &'static str = "norun.v1.RegistryService"; + } +} diff --git a/crates/norun-grpc-interface/src/lib.rs b/crates/norun-grpc-interface/src/lib.rs new file mode 100644 index 0000000..739784a --- /dev/null +++ b/crates/norun-grpc-interface/src/lib.rs @@ -0,0 +1,5 @@ +pub mod grpc { + include!("./grpc/norun.v1.rs"); +} + +pub use grpc::*; diff --git a/crates/norun/Cargo.toml b/crates/norun/Cargo.toml index 5c3e8ed..8800af9 100644 --- a/crates/norun/Cargo.toml +++ b/crates/norun/Cargo.toml @@ -4,8 +4,23 @@ version = "0.1.0" edition = "2024" [dependencies] +norun-grpc-interface.workspace = true + anyhow = "1.0.98" clap = { version = "4.5.40", features = ["derive", "env"] } +serde = { version = "1.0.219", features = ["derive"] } tokio = { version = "1.46.1", features = ["full"] } +toml = "0.8.23" tracing = { version = "0.1.41", features = ["log"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } + +bytes = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +tonic = { workspace = true } +tokio-util = "0.7.15" +async-trait = "0.1.88" +notmad = "0.7.2" + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/crates/norun/src/cli.rs b/crates/norun/src/cli.rs index 1483146..ee65dd8 100644 --- a/crates/norun/src/cli.rs +++ b/crates/norun/src/cli.rs @@ -1,29 +1,40 @@ use clap::{Parser, Subcommand}; -use crate::cli::publish::PublishCommand; +use crate::{ + cli::{publish::PublishCommand, serve::ServeCommand}, + state::ClientState, +}; mod publish; +mod serve; #[derive(Parser)] #[command(author, version, about)] struct Cli { #[command(subcommand)] subcommands: CliSubcommands, + + #[arg( + long = "server-url", + env = "NORUN_SERVER_URL", + default_value = "http://localhost:4242" + )] + server_url: String, } #[derive(Subcommand)] enum CliSubcommands { Publish(PublishCommand), + Serve(ServeCommand), } pub async fn execute() -> anyhow::Result<()> { let cmd = Cli::parse(); - let state = State::new(); + let state = ClientState::new(&cmd.server_url); match cmd.subcommands { - CliSubcommands::Publish(cmd) => cmd.execute(&state), + CliSubcommands::Publish(cmd) => cmd.execute(&state).await, + CliSubcommands::Serve(cmd) => cmd.execute().await, } - - Ok(()) } diff --git a/crates/norun/src/cli/publish.rs b/crates/norun/src/cli/publish.rs index e9f48ec..281f4c9 100644 --- a/crates/norun/src/cli/publish.rs +++ b/crates/norun/src/cli/publish.rs @@ -1,8 +1,26 @@ -#[derive(clap::Parser)] -pub struct PublishCommand {} +use std::path::PathBuf; + +use crate::{grpc_client::GrpcClientState, models::ProjectTag, project_file, state::ClientState}; + +#[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, +} impl PublishCommand { - pub async fn execute(&self, state: &State) -> anyhow::Result<()> { + #[tracing::instrument(skip(state), level = "trace")] + pub async fn execute(&self, state: &ClientState) -> anyhow::Result<()> { + tracing::debug!("running norun publish"); + + let project_file = project_file::parse_file(&self.project_path).await?; + + // FIXME: 2. Load resources + state.grpc_client().publish(&project_file).await?; + Ok(()) } } diff --git a/crates/norun/src/cli/serve.rs b/crates/norun/src/cli/serve.rs new file mode 100644 index 0000000..b4be178 --- /dev/null +++ b/crates/norun/src/cli/serve.rs @@ -0,0 +1,31 @@ +use std::net::SocketAddr; + +use notmad::Mad; + +use crate::{grpc_server::GrpcServer, state::ServerState}; + +#[derive(clap::Parser)] +pub struct ServeCommand { + #[arg( + long = "grpc-host", + env = "NORUN_GRPC_HOST", + default_value = "127.0.0.1:4242" + )] + grpc_host: SocketAddr, +} + +impl ServeCommand { + pub async fn execute(&self) -> anyhow::Result<()> { + let state = ServerState {}; + + Mad::builder() + .add(GrpcServer { + host: self.grpc_host, + state: state.clone(), + }) + .run() + .await?; + + Ok(()) + } +} diff --git a/crates/norun/src/grpc_client.rs b/crates/norun/src/grpc_client.rs new file mode 100644 index 0000000..453098e --- /dev/null +++ b/crates/norun/src/grpc_client.rs @@ -0,0 +1,72 @@ +use norun_grpc_interface::{PublishRequest, registry_service_client::RegistryServiceClient}; +use tokio::sync::OnceCell; +use tonic::transport::Channel; + +use crate::{project_file::ProjectFile, state::ClientState}; + +pub struct GrpcClient { + url: String, + registry_client: OnceCell>, +} + +impl GrpcClient { + #[tracing::instrument(skip(self), level = "trace")] + pub async fn publish(&self, project_file: &ProjectFile) -> anyhow::Result<()> { + tracing::trace!("calling publish via. grpc on registry"); + + let mut registry_client = self.get_registry_client().await?; + + let res = registry_client + .publish(PublishRequest { + project: Some(project_file.into()), + }) + .await?; + let _res = res.into_inner(); + + Ok(()) + } + + async fn get_registry_client(&self) -> anyhow::Result> { + let client = self + .registry_client + .get_or_try_init(move || async move { + let channel = Channel::from_shared(self.url.clone())?.connect().await?; + let client = RegistryServiceClient::new(channel); + + Ok::<_, anyhow::Error>(client) + }) + .await?; + + Ok(client.clone()) + } +} + +impl From<&ProjectFile> for norun_grpc_interface::Project { + fn from(value: &ProjectFile) -> Self { + value.clone().into() + } +} + +impl From for norun_grpc_interface::Project { + fn from(value: ProjectFile) -> Self { + Self { + name: value.project.name, + image: value.container.image, + version: value.container.version, + port: value.expose.and_then(|e| e.port), + } + } +} + +pub trait GrpcClientState { + fn grpc_client(&self) -> GrpcClient; +} + +impl GrpcClientState for ClientState { + fn grpc_client(&self) -> GrpcClient { + GrpcClient { + url: self.norun_server_url.clone(), + registry_client: OnceCell::default(), + } + } +} diff --git a/crates/norun/src/grpc_server.rs b/crates/norun/src/grpc_server.rs new file mode 100644 index 0000000..e1581d2 --- /dev/null +++ b/crates/norun/src/grpc_server.rs @@ -0,0 +1,47 @@ +use std::net::SocketAddr; + +use norun_grpc_interface::registry_service_server::RegistryServiceServer; +use notmad::MadError; +use tokio_util::sync::CancellationToken; + +use crate::{grpc_server::registry::GrpcRegistryService, state::ServerState}; + +mod registry; + +pub struct GrpcServer { + pub host: SocketAddr, + pub state: ServerState, +} + +impl GrpcServer { + pub async fn serve(&self, cancellation_token: CancellationToken) -> anyhow::Result<()> { + tracing::info!("serving grpc on {}", self.host); + + tonic::transport::Server::builder() + .add_service(RegistryServiceServer::new(GrpcRegistryService { + state: self.state.clone(), + })) + .serve_with_shutdown( + self.host, + async move { cancellation_token.cancelled().await }, + ) + .await?; + + Ok(()) + } +} + +#[async_trait::async_trait] +impl notmad::Component for GrpcServer { + fn name(&self) -> Option { + Some("norun/gprc-server".into()) + } + + async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError> { + self.serve(cancellation_token) + .await + .map_err(MadError::Inner)?; + + Ok(()) + } +} diff --git a/crates/norun/src/grpc_server/registry.rs b/crates/norun/src/grpc_server/registry.rs new file mode 100644 index 0000000..9d142a0 --- /dev/null +++ b/crates/norun/src/grpc_server/registry.rs @@ -0,0 +1,33 @@ +use norun_grpc_interface::{registry_service_server::RegistryService, *}; + +use crate::{server::services::registry::RegistryServiceState, state::ServerState}; + +pub struct GrpcRegistryService { + pub state: ServerState, +} + +#[async_trait::async_trait] +impl RegistryService for GrpcRegistryService { + #[tracing::instrument(skip(self), level = "debug")] + async fn publish( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + tracing::debug!("publish called"); + + let req = request.into_inner(); + + let project = req + .project + .ok_or(tonic::Status::invalid_argument("a project is required"))?; + + self.state + .registry_service() + .store(&project) + .await + .inspect_err(|e| tracing::warn!("failed to handle storage of registry item: {}", e)) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + + Ok(tonic::Response::new(PublishResponse {})) + } +} diff --git a/crates/norun/src/main.rs b/crates/norun/src/main.rs index be4b64e..a737a5f 100644 --- a/crates/norun/src/main.rs +++ b/crates/norun/src/main.rs @@ -1,6 +1,14 @@ use tracing_subscriber::EnvFilter; mod cli; +mod models; +mod project_file; +mod state; + +mod server; + +mod grpc_client; +mod grpc_server; #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/crates/norun/src/models.rs b/crates/norun/src/models.rs new file mode 100644 index 0000000..d00f0ca --- /dev/null +++ b/crates/norun/src/models.rs @@ -0,0 +1,2 @@ +pub mod project_tag; +pub use project_tag::*; diff --git a/crates/norun/src/models/project_tag.rs b/crates/norun/src/models/project_tag.rs new file mode 100644 index 0000000..c635b83 --- /dev/null +++ b/crates/norun/src/models/project_tag.rs @@ -0,0 +1,110 @@ +use std::str::FromStr; + +// ProjectTag can be a destination@name:version +#[derive(Clone, Debug, PartialEq)] +pub struct ProjectTag { + pub destination: String, + pub name: String, + pub version: String, +} + +impl FromStr for ProjectTag { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let (destination, rest) = s.split_once('@').ok_or(anyhow::anyhow!("failed to find an '@' in your project tag, make sure it looks like 'destination@name:version'"))?; + if destination.is_empty() { + anyhow::bail!("destination is required like so 'destination@name:version'"); + } + + let (name, version) = rest.split_once(':').ok_or(anyhow::anyhow!("failed to find an ':' in your project tag, make sure it looks like 'destination@name:version'"))?; + if name.is_empty() { + anyhow::bail!("name is required like so 'destination@name:version'"); + } + if version.is_empty() { + anyhow::bail!("version is required like so 'destination@name:version'"); + } + + Ok(Self { + destination: destination.to_string(), + name: name.to_string(), + version: version.to_string(), + }) + } +} + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use crate::models::ProjectTag; + + #[test] + fn happy_path() -> anyhow::Result<()> { + let raw = "destination@name:version"; + let expected = ProjectTag { + destination: "destination".to_string(), + name: "name".to_string(), + version: "version".to_string(), + }; + + let actual = ProjectTag::from_str(raw)?; + + pretty_assertions::assert_eq!(expected, actual); + + Ok(()) + } + + #[test] + fn missing_destination_fails() -> anyhow::Result<()> { + let raw = "@name:version"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } + + #[test] + fn missing_destination_tag_fails() -> anyhow::Result<()> { + let raw = "name:version"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } + + #[test] + fn missing_name_fails() -> anyhow::Result<()> { + let raw = "destination@:version"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } + + #[test] + fn missing_name_tag_fails() -> anyhow::Result<()> { + let raw = "destination@:version"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } + #[test] + fn missing_version_fails() -> anyhow::Result<()> { + let raw = "destination@name:"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } + + #[test] + fn missing_version_tag_fails() -> anyhow::Result<()> { + let raw = "destination@name:"; + + ProjectTag::from_str(raw).expect_err("test expected err"); + + Ok(()) + } +} diff --git a/crates/norun/src/project_file.rs b/crates/norun/src/project_file.rs new file mode 100644 index 0000000..15c86e2 --- /dev/null +++ b/crates/norun/src/project_file.rs @@ -0,0 +1,88 @@ +use std::path::Path; + +use anyhow::Context; +use serde::Deserialize; + +const NORUN_PROJECT_FILE_NAME: &str = "norun.toml"; + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct ProjectFile { + pub project: ProjectDecl, + pub container: ContainerDecl, + pub expose: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct ProjectDecl { + pub name: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct ContainerDecl { + pub image: String, + pub version: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct ExposeDecl { + pub port: Option, +} + +pub fn parse(content: &str) -> anyhow::Result { + let project_file: ProjectFile = toml::from_str(content)?; + + Ok(project_file) +} + +pub async fn parse_file(path: &Path) -> anyhow::Result { + let path = if let Some(file_name) = path.file_name() + && file_name == NORUN_PROJECT_FILE_NAME + { + path + } else { + &path.join(NORUN_PROJECT_FILE_NAME) + }; + + let file = tokio::fs::read_to_string(path) + .await + .context("failed to read project file in the given directory")?; + + parse(&file) +} + +#[cfg(test)] +mod test { + use crate::project_file::*; + + #[test] + fn can_parse_file_happy_path() -> anyhow::Result<()> { + let raw = r#" +[project] +name = "hello-world" + +[container] +image = "kasperhermansen/hello-world" +version = "latest" # default + +[expose] +port = 8080 + "#; + + let expected = ProjectFile { + project: ProjectDecl { + name: "hello-world".into(), + }, + container: ContainerDecl { + image: "kasperhermansen/hello-world".into(), + version: "latest".into(), + }, + expose: Some(ExposeDecl { port: Some(8080) }), + }; + + let actual = parse(raw)?; + + pretty_assertions::assert_eq!(expected, actual); + + Ok(()) + } +} diff --git a/crates/norun/src/server.rs b/crates/norun/src/server.rs new file mode 100644 index 0000000..4e379ae --- /dev/null +++ b/crates/norun/src/server.rs @@ -0,0 +1 @@ +pub mod services; diff --git a/crates/norun/src/server/services.rs b/crates/norun/src/server/services.rs new file mode 100644 index 0000000..d108990 --- /dev/null +++ b/crates/norun/src/server/services.rs @@ -0,0 +1 @@ +pub mod registry; diff --git a/crates/norun/src/server/services/registry.rs b/crates/norun/src/server/services/registry.rs new file mode 100644 index 0000000..720644d --- /dev/null +++ b/crates/norun/src/server/services/registry.rs @@ -0,0 +1,42 @@ +use std::{ + collections::BTreeMap, + sync::{Arc, LazyLock}, +}; + +use norun_grpc_interface::Project; +use tokio::sync::Mutex; + +use crate::state::ServerState; + +#[derive(Clone)] +pub struct RegistryService { + store: Arc>>, +} + +impl RegistryService { + pub async fn store(&self, project: &Project) -> anyhow::Result<()> { + tracing::debug!("storing project"); + + let mut store = self.store.lock().await; + + store.push(project.clone()); + + tracing::debug!("currently has {} in storage", store.len()); + + Ok(()) + } +} + +pub trait RegistryServiceState { + fn registry_service(&self) -> RegistryService; +} + +impl RegistryServiceState for ServerState { + fn registry_service(&self) -> RegistryService { + static SERVICE: LazyLock = LazyLock::new(|| RegistryService { + store: Arc::default(), + }); + + SERVICE.clone() + } +} diff --git a/crates/norun/src/state.rs b/crates/norun/src/state.rs new file mode 100644 index 0000000..caf74ae --- /dev/null +++ b/crates/norun/src/state.rs @@ -0,0 +1,21 @@ +#[derive(Clone)] +pub struct ClientState { + pub norun_server_url: String, +} + +impl ClientState { + pub fn new(norun_server_url: &str) -> Self { + Self { + norun_server_url: norun_server_url.to_string(), + } + } +} + +#[derive(Clone)] +pub struct ServerState {} + +impl ServerState { + pub fn new() -> Self { + Self {} + } +} diff --git a/examples/hello-world/norun.toml b/examples/hello-world/norun.toml new file mode 100644 index 0000000..fc6e67d --- /dev/null +++ b/examples/hello-world/norun.toml @@ -0,0 +1,9 @@ +[project] +name = "hello-world" + +[container] +image = "kasperhermansen/hello-world" +version = "latest" + +[expose] +port = 8080 diff --git a/interface/proto/norun/v1/registry.proto b/interface/proto/norun/v1/registry.proto new file mode 100644 index 0000000..b1a16db --- /dev/null +++ b/interface/proto/norun/v1/registry.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package norun.v1; + +service RegistryService { + rpc Publish(PublishRequest) returns (PublishResponse) {} +} + +message PublishRequest { + Project project = 1; +} +message PublishResponse {} + +message Project { + string name = 1; + + string image = 2; + string version = 3; + + optional uint32 port = 4; +}