commit 353b8c1eb0ebb814413bd1492973a5a728a60e97 Author: kjuulh Date: Mon Jan 5 19:10:00 2026 +0100 feat: add very basic in memory coordination Signed-off-by: kjuulh diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..7d36638 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,2 @@ +kind: template +load: cuddle-rust-lib-plan.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c4c004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.cuddle/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dfd0b44 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1028 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "insta" +version = "1.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nocontrol" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "hex", + "insta", + "jiff", + "rand", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "tracing-test", + "uuid", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +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.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "syn" +version = "2.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cd1c16e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.dependencies] +nocontrol = { path = "crates/nocontrol" } + +anyhow = { version = "1.0.71" } +tokio = { version = "1", features = ["full"] } +tracing = { version = "0.1", features = ["log"] } diff --git a/crates/nocontrol/Cargo.toml b/crates/nocontrol/Cargo.toml new file mode 100644 index 0000000..61ed663 --- /dev/null +++ b/crates/nocontrol/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nocontrol" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +async-trait = "0.1.89" +hex = "0.4.3" +jiff = { version = "0.2.17", features = ["serde"] } +rand = "0.9.2" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.148" +sha2 = "0.10.9" +tokio.workspace = true +tokio-util = "0.7.18" +tracing.workspace = true +uuid = { version = "1.19.0", features = ["serde", "v4", "v7"] } + +[dev-dependencies] +insta = "1.46.0" +tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } +tracing-test = { version = "0.2.5", features = ["no-env-filter"] } diff --git a/crates/nocontrol/examples/kubernetes-like/main.rs b/crates/nocontrol/examples/kubernetes-like/main.rs new file mode 100644 index 0000000..8441850 --- /dev/null +++ b/crates/nocontrol/examples/kubernetes-like/main.rs @@ -0,0 +1,200 @@ +use std::io::{BufRead, Write}; + +use async_trait::async_trait; +use nocontrol::{ + manifests::{Manifest, ManifestMetadata, ManifestState}, + Operator, +}; +use serde::{Deserialize, Serialize}; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let output_file = std::fs::File::create("target/nocontrol.log")?; + + tracing_subscriber::fmt() + // .pretty() + .with_env_filter(EnvFilter::from_default_env()) + .with_writer(output_file) + .with_file(false) + .with_line_number(false) + .with_target(false) + .without_time() + .init(); + + let operator = MyOperator {}; + + let mut control_plane = nocontrol::ControlPlane::new(operator); + // control_plane.with_deadline(std::time::Duration::from_secs(4)); + + tokio::spawn({ + let control_plane = control_plane.clone(); + async move { + control_plane + .add_manifest(Manifest { + name: "some-manifest".into(), + metadata: ManifestMetadata {}, + spec: Specifications::Deployment(DeploymentControllerManifest { + name: "some-name".into(), + }), + }) + .await + .unwrap(); + + loop { + let rand = { + use rand::prelude::*; + let mut rng = rand::rng(); + rng.random_range(2..5) + }; + + tokio::time::sleep(std::time::Duration::from_secs(rand)).await; + + let random = uuid::Uuid::now_v7(); + + control_plane + .add_manifest(Manifest { + name: "some-manifest".into(), + metadata: ManifestMetadata {}, + spec: Specifications::Deployment(DeploymentControllerManifest { + name: format!("some-changed-name: {}", random), + }), + }) + .await + .unwrap(); + } + } + }); + + // Debugging shell + tokio::spawn({ + let control_plane = control_plane.clone(); + + async move { + let ui = Ui {}; + + loop { + ui.write("> "); + let cmd = ui.read_line(); + + let items = cmd.split(" ").map(|t| t.to_string()).collect::>(); + + let (command, args) = match &items[..] { + [first, rest @ ..] => (first, rest.to_vec()), + //[first] => (first, vec![]), + _ => { + ui.writeln("invalid command"); + continue; + } + }; + + match (command.as_str(), args.as_slice()) { + ("get", _) => { + // get all for now + let manifests = control_plane + .get_manifests() + .await + .inspect_err(|e| ui.writeln(format!("get failed: {e:#}"))) + .unwrap(); + + ui.writeln("listing manifests"); + + for manifest in manifests { + ui.writeln(format!(" - {}", manifest.manifest.name)); + } + } + ("describe", [manifest_name, ..]) => { + let manifests = control_plane + .get_manifests() + .await + .inspect_err(|e| ui.writeln(format!("get failed: {e:#}"))) + .unwrap(); + + if let Some(manifest) = + manifests.iter().find(|m| &m.manifest.name == manifest_name) + { + let output = serde_json::to_string_pretty(&manifest).unwrap(); + ui.writeln(output); + } + } + (cmd, _) => ui.writeln(format!("command is not implemented: {}", cmd)), + } + + ui.writeln(""); + } + } + }); + + control_plane.execute().await?; + + Ok(()) +} + +#[derive(Clone)] +pub struct MyOperator {} + +#[async_trait] +impl Operator for MyOperator { + type Specifications = Specifications; + + async fn reconcile( + &self, + desired_manifest: &mut ManifestState, + ) -> anyhow::Result<()> { + let now = jiff::Timestamp::now(); + + desired_manifest.status.status = nocontrol::manifests::ManifestStatusState::Started; + desired_manifest.updated = now; + + match &desired_manifest.manifest.spec { + Specifications::Deployment(spec) => { + tracing::info!( + "reconciliation was called for name = {}, value = {}", + desired_manifest.manifest.name, + spec.name + ) + } + } + + desired_manifest.status.status = nocontrol::manifests::ManifestStatusState::Running; + desired_manifest.updated = now; + + Ok(()) + } +} + +#[derive(Clone, Serialize)] +pub enum Specifications { + Deployment(DeploymentControllerManifest), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeploymentControllerManifest { + name: String, +} + +pub struct Ui {} + +impl Ui { + pub fn write(&self, msg: &str) { + let mut stderr = std::io::stderr().lock(); + stderr.write_all(msg.as_bytes()).unwrap(); + stderr.flush().unwrap() + } + pub fn writeln(&self, msg: impl AsRef) { + let msg = msg.as_ref(); + + let mut stderr = std::io::stderr().lock(); + stderr.write_all(msg.as_bytes()).unwrap(); + writeln!(stderr).unwrap(); + stderr.flush().unwrap() + } + pub fn read_line(&self) -> String { + let mut stdin = std::io::stdin().lock(); + let mut output = String::new(); + + stdin.read_line(&mut output).unwrap(); + + output.trim().to_string() + } +} diff --git a/crates/nocontrol/src/control_plane.rs b/crates/nocontrol/src/control_plane.rs new file mode 100644 index 0000000..f620855 --- /dev/null +++ b/crates/nocontrol/src/control_plane.rs @@ -0,0 +1,77 @@ +use std::marker::PhantomData; + +use tokio_util::sync::CancellationToken; + +use crate::{ + control_plane::{backing_store::BackingStore, reconciler::Reconciler}, + manifests::{Manifest, ManifestState}, + Operator, +}; + +pub mod backing_store; +pub mod reconciler; + +#[derive(Clone)] +pub struct ControlPlane { + reconciler: Reconciler, + worker_id: uuid::Uuid, + store: BackingStore, + + deadline: Option, +} + +impl ControlPlane { + pub fn new(operator: TOperator) -> Self { + let worker_id = uuid::Uuid::now_v7(); + let store = BackingStore::::new(); + + let reconciler = Reconciler::new(worker_id, &store, operator); + + Self { + reconciler, + worker_id, + deadline: None, + store, + } + } + + pub fn with_deadline(&mut self, deadline: std::time::Duration) -> &mut Self { + self.deadline = Some(deadline); + + self + } + + pub async fn execute(&self) -> anyhow::Result<()> { + tracing::info!(worker_id = %self.worker_id, "starting control plane"); + + let cancellation_token = CancellationToken::new(); + let child_token = cancellation_token.child_token(); + if let Some(deadline) = self.deadline { + tokio::spawn(async move { + tokio::time::sleep(deadline).await; + cancellation_token.cancel(); + }); + } + + self.reconciler.reconcile(&child_token).await?; + + Ok(()) + } + + pub async fn add_manifest( + &self, + manifest: Manifest, + ) -> anyhow::Result<()> { + tracing::info!(manifest.name, "adding manifest"); + + self.store.upsert_manifest(manifest).await?; + + Ok(()) + } + + pub async fn get_manifests( + &self, + ) -> anyhow::Result>> { + self.store.get_manifests().await + } +} diff --git a/crates/nocontrol/src/control_plane/backing_store.rs b/crates/nocontrol/src/control_plane/backing_store.rs new file mode 100644 index 0000000..f7c28c4 --- /dev/null +++ b/crates/nocontrol/src/control_plane/backing_store.rs @@ -0,0 +1,152 @@ +use std::sync::Arc; + +use jiff::ToSpan; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use tokio::sync::RwLock; + +use crate::manifests::{ + Manifest, ManifestLease, ManifestState, ManifestStatus, ManifestStatusState, WorkerId, +}; + +#[derive(Clone)] +pub struct BackingStore { + manifests: Arc>>>, +} + +impl BackingStore { + pub fn new() -> Self { + Self { + manifests: Arc::new(RwLock::new(Vec::new())), + } + } + + pub async fn get_owned_and_potential_leases(&self) -> anyhow::Result>> { + let now = jiff::Timestamp::now().checked_sub(1.second())?; + let manifests = self + .manifests + .read() + .await + .iter() + .filter(|m| match &m.lease { + Some(lease) if lease.last_seen < now => true, + Some(_lease) => false, + None => true, + }) + .cloned() + .collect::>(); + + Ok(manifests) + } + + pub async fn get_manifests(&self) -> anyhow::Result>> { + Ok(self.manifests.read().await.clone()) + } + + pub async fn update_lease(&self, manifest_state: &ManifestState) -> anyhow::Result<()> { + tracing::trace!(manifest_state.manifest.name, "updating lease"); + let mut manifests = self.manifests.write().await; + + match manifests + .iter_mut() + .find(|m| m.manifest.name == manifest_state.manifest.name) + { + Some(manifest) => { + let mut manifest_state = manifest_state.clone(); + if let Some(lease) = manifest_state.lease.as_mut() { + lease.last_seen = jiff::Timestamp::now(); + } + + manifest.lease = manifest_state.lease + } + None => anyhow::bail!("manifest is not found"), + } + + Ok(()) + } + + pub async fn acquire_lease( + &self, + manifest_state: &ManifestState, + worker_id: &WorkerId, + ) -> anyhow::Result<()> { + tracing::trace!(manifest_state.manifest.name, "acquiring lease"); + let mut manifests = self.manifests.write().await; + + match manifests + .iter_mut() + .find(|m| m.manifest.name == manifest_state.manifest.name) + { + Some(manifest) => { + let mut manifest_state = manifest_state.clone(); + manifest_state.lease = Some(ManifestLease { + owner: *worker_id, + last_seen: jiff::Timestamp::now(), + }); + manifest.lease = manifest_state.lease + } + None => anyhow::bail!("manifest is not found"), + } + + Ok(()) + } + + pub async fn upsert_manifest(&self, manifest: Manifest) -> anyhow::Result<()> { + let mut manifests = self.manifests.write().await; + + let now = jiff::Timestamp::now(); + match manifests + .iter_mut() + .find(|m| m.manifest.name == manifest.name) + { + Some(current_manifest) => { + tracing::debug!("updating manifest"); + + current_manifest.manifest = manifest; + current_manifest.updated = now; + } + None => { + tracing::debug!("adding manifest"); + let content = serde_json::to_vec(&manifest)?; + let output = Sha256::digest(&content); + + manifests.push(ManifestState { + manifest, + manifest_hash: output[..].to_vec(), + generation: 0, + status: ManifestStatus { + status: ManifestStatusState::Pending, + events: Vec::default(), + }, + created: now, + updated: now, + lease: None, + }); + } + } + + Ok(()) + } + + pub async fn update_state(&self, manifest: &ManifestState) -> anyhow::Result<()> { + let mut manifests = self.manifests.write().await; + + let Some(current_manifest) = manifests + .iter_mut() + .find(|m| m.manifest.name == manifest.manifest.name) + else { + anyhow::bail!( + "manifest state was not found for {}", + manifest.manifest.name + ) + }; + + let manifest = manifest.clone(); + + current_manifest.generation += 1; + current_manifest.status = manifest.status; + current_manifest.updated = manifest.updated; + + Ok(()) + } +} diff --git a/crates/nocontrol/src/control_plane/reconciler.rs b/crates/nocontrol/src/control_plane/reconciler.rs new file mode 100644 index 0000000..be14627 --- /dev/null +++ b/crates/nocontrol/src/control_plane/reconciler.rs @@ -0,0 +1,77 @@ +use anyhow::Context; +use tokio_util::sync::CancellationToken; + +use crate::{control_plane::backing_store::BackingStore, manifests::WorkerId, Operator}; + +#[derive(Clone)] +pub struct Reconciler { + worker_id: WorkerId, + store: BackingStore, + operator: T, +} + +impl Reconciler { + pub fn new(worker_id: WorkerId, store: &BackingStore, operator: T) -> Self { + Self { + worker_id, + store: store.clone(), + operator, + } + } + + pub async fn reconcile(&self, cancellation_token: &CancellationToken) -> anyhow::Result<()> { + let now = jiff::Timestamp::now(); + tracing::debug!(%self.worker_id, %now, "running reconciler"); + + loop { + let now = jiff::Timestamp::now(); + if cancellation_token.is_cancelled() { + break; + } + tracing::trace!(%self.worker_id, %now, "reconciler iteration"); + + let mut our_manifests = Vec::new(); + // 1. read manifests from a backing store + for manifest_state in self.store.get_owned_and_potential_leases().await? { + // 3. Lease the manifest + match &manifest_state.lease { + Some(lease) if lease.owner == self.worker_id => { + // We own the lease, update + self.store + .update_lease(&manifest_state) + .await + .context("update lease")?; + our_manifests.push(manifest_state.clone()); + } + None => { + // 2. If no lease + // Acquire lease + self.store + .acquire_lease(&manifest_state, &self.worker_id) + .await + .context("acquire lease")?; + our_manifests.push(manifest_state.clone()); + } + _ => { + // Skipping manifest, as it is not vaid + continue; + } + } + } + + // 4. Check desired vs actual + for manifest in our_manifests.iter_mut() { + // Currently periodic sync, + // TODO: this should also be made event based + self.operator.reconcile(manifest).await?; + self.store.update_state(manifest).await?; + } + + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + + tracing::debug!("reconciler shutting down"); + + Ok(()) + } +} diff --git a/crates/nocontrol/src/lib.rs b/crates/nocontrol/src/lib.rs new file mode 100644 index 0000000..d61fa44 --- /dev/null +++ b/crates/nocontrol/src/lib.rs @@ -0,0 +1,17 @@ +mod control_plane; +pub mod manifests; + +pub use control_plane::ControlPlane; +use serde::Serialize; + +use crate::manifests::{Manifest, ManifestState}; + +#[async_trait::async_trait] +pub trait Operator { + type Specifications: Clone + Serialize; + + async fn reconcile( + &self, + desired_manifest: &mut ManifestState, + ) -> anyhow::Result<()>; +} diff --git a/crates/nocontrol/src/manifests.rs b/crates/nocontrol/src/manifests.rs new file mode 100644 index 0000000..d946ea1 --- /dev/null +++ b/crates/nocontrol/src/manifests.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ManifestState { + pub manifest: Manifest, + pub manifest_hash: Vec, + pub generation: u64, + pub status: ManifestStatus, + + pub created: jiff::Timestamp, + pub updated: jiff::Timestamp, + + pub lease: Option, +} + +pub type WorkerId = uuid::Uuid; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ManifestLease { + pub owner: WorkerId, + pub last_seen: jiff::Timestamp, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Manifest { + pub name: String, + pub metadata: ManifestMetadata, + pub spec: T, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ManifestStatus { + pub status: ManifestStatusState, + pub events: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ManifestStatusState { + Pending, + Started, + Running, + Stopping, + Deleting, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ManifestEvent { + pub owner: WorkerId, + pub created: u64, + pub message: String, + pub state: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManifestMetadata {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "@type")] +pub enum ManifestSpecification { + SchemaApplication {}, +} + +#[cfg(test)] +mod test { + use crate::manifests::*; + + #[test] + fn manifest() -> anyhow::Result<()> { + let manifest = Manifest { + name: "ingest".into(), + metadata: ManifestMetadata {}, + spec: ManifestSpecification::SchemaApplication {}, + }; + + insta::assert_debug_snapshot!(manifest); + + Ok(()) + } +} diff --git a/crates/nocontrol/src/snapshots/nocontrol__manifests__test__manifest.snap b/crates/nocontrol/src/snapshots/nocontrol__manifests__test__manifest.snap new file mode 100644 index 0000000..fad3ae3 --- /dev/null +++ b/crates/nocontrol/src/snapshots/nocontrol__manifests__test__manifest.snap @@ -0,0 +1,9 @@ +--- +source: crates/nocontrol/src/manifests.rs +expression: manifest +--- +Manifest { + name: "ingest", + metadata: ManifestMetadata, + spec: SchemaApplication, +} diff --git a/crates/nocontrol/tests/mod.rs b/crates/nocontrol/tests/mod.rs new file mode 100644 index 0000000..8dea42b --- /dev/null +++ b/crates/nocontrol/tests/mod.rs @@ -0,0 +1,91 @@ +use async_trait::async_trait; +use nocontrol::{ + manifests::{Manifest, ManifestMetadata, ManifestState}, + Operator, +}; +use serde::{Deserialize, Serialize}; +use tracing_test::traced_test; + +#[tokio::test] +#[traced_test] +async fn test_can_run_reconciler() -> anyhow::Result<()> { + let operator = MyOperator {}; + + let mut control_plane = nocontrol::ControlPlane::new(operator); + control_plane.with_deadline(std::time::Duration::from_secs(3)); + + tokio::spawn({ + let control_plane = control_plane.clone(); + async move { + control_plane + .add_manifest(Manifest { + name: "some-manifest".into(), + metadata: ManifestMetadata {}, + spec: Specifications::Deployment(DeploymentControllerManifest { + name: "some-name".into(), + }), + }) + .await + .unwrap(); + + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + control_plane + .add_manifest(Manifest { + name: "some-manifest".into(), + metadata: ManifestMetadata {}, + spec: Specifications::Deployment(DeploymentControllerManifest { + name: "some-changed-name".into(), + }), + }) + .await + .unwrap(); + } + }); + + control_plane.execute().await?; + + Err(anyhow::anyhow!("fail")) +} + +#[derive(Clone)] +pub struct MyOperator {} + +#[async_trait] +impl Operator for MyOperator { + type Specifications = Specifications; + + async fn reconcile( + &self, + desired_manifest: &mut ManifestState, + ) -> anyhow::Result<()> { + let now = jiff::Timestamp::now(); + + desired_manifest.status.status = nocontrol::manifests::ManifestStatusState::Started; + desired_manifest.updated = now; + + match &desired_manifest.manifest.spec { + Specifications::Deployment(spec) => { + tracing::info!( + "reconciliation was called for name = {}, value = {}", + desired_manifest.manifest.name, + spec.name + ) + } + } + desired_manifest.status.status = nocontrol::manifests::ManifestStatusState::Running; + desired_manifest.updated = now; + + Ok(()) + } +} + +#[derive(Clone, Serialize)] +pub enum Specifications { + Deployment(DeploymentControllerManifest), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeploymentControllerManifest { + name: String, +} diff --git a/cuddle.yaml b/cuddle.yaml new file mode 100644 index 0000000..afbe84d --- /dev/null +++ b/cuddle.yaml @@ -0,0 +1,17 @@ +# yaml-language-server: $schema=https://git.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json + +base: "git@git.kjuulh.io:kjuulh/cuddle-rust-lib-plan.git" + +vars: + service: "nocontrol" + registry: kasperhermansen + +please: + project: + owner: kjuulh + repository: "nocontrol" + branch: main + settings: + api_url: "https://git.kjuulh.io" + actions: + rust: diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..0d41068 --- /dev/null +++ b/mise.toml @@ -0,0 +1,10 @@ +[env] +RUST_LOG = "nocontrol=debug,info" + +[tasks.test] +alias = "t" +run = "cargo nextest run" + +[tasks.example] +alias = "e" +run = "cargo run --example kubernetes-like"