diff --git a/Cargo.lock b/Cargo.lock index 358b05c..2eac8c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,12 +141,24 @@ dependencies = [ "num-traits", ] +[[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 = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -173,6 +185,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -203,6 +221,56 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bollard" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.47.1-rc.27.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "borsh" version = "1.5.7" @@ -363,6 +431,29 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "container-simulate" +version = "0.1.0" +dependencies = [ + "async-trait", + "clap", + "path-clean", + "shared", + "testcontainers", + "tokio", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -410,9 +501,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -424,8 +515,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -441,13 +542,38 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.110", ] @@ -530,12 +656,29 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -551,6 +694,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[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 = "etcetera" version = "0.8.0" @@ -562,6 +715,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.59.0", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -573,6 +737,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -617,6 +793,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[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" @@ -661,6 +852,17 @@ 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 2.0.110", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -679,8 +881,10 @@ 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", @@ -691,9 +895,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -789,11 +993,146 @@ dependencies = [ [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] @@ -928,6 +1267,17 @@ dependencies = [ "icu_properties", ] +[[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", + "serde", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -936,6 +1286,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", + "serde", + "serde_core", ] [[package]] @@ -1016,9 +1368,9 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.5.18", ] [[package]] @@ -1032,6 +1384,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.1" @@ -1109,9 +1467,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", @@ -1171,6 +1529,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "ordered-float" version = "4.6.0" @@ -1228,11 +1592,42 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.110", +] + +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1438,13 +1833,42 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -1530,9 +1954,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -1564,6 +1988,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.35" @@ -1578,6 +2015,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -1610,6 +2068,39 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1734,7 +2225,7 @@ version = "1.0.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365d236217f5daa4f40d3c9998ff3921351b53472da50308e384388162353b3a" dependencies = [ - "darling", + "darling 0.20.11", "heck 0.4.1", "proc-macro2", "quote", @@ -1760,9 +2251,9 @@ dependencies = [ [[package]] name = "sea-schema" -version = "0.17.0-rc.12" +version = "0.17.0-rc.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cb79f2d0fe3a3ca7852eb1c61590452674700fdeb9f07656b9eb16f8d1990b" +checksum = "da2e92a98855cfdc2e32daac3f48517a3d9911209db012e5c74af1ff13a24946" dependencies = [ "async-trait", "sea-query", @@ -1789,6 +2280,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.228" @@ -1832,6 +2346,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1844,6 +2369,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1973,7 +2529,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bigdecimal", "bytes", "chrono", @@ -1987,7 +2543,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.12.0", "log", "memchr", "once_cell", @@ -2053,9 +2609,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bigdecimal", - "bitflags", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -2100,14 +2656,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bigdecimal", - "bitflags", + "bitflags 2.10.0", "byteorder", "chrono", "crc", "dotenvy", - "etcetera", + "etcetera 0.8.0", "futures-channel", "futures-core", "futures-util", @@ -2192,6 +2748,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.110", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "strum" version = "0.27.2" @@ -2243,6 +2822,35 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "testcontainers" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bb7577dca13ad86a78e8271ef5d322f37229ec83b8d98da6d996c588a1ddb1" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera 0.10.0", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + [[package]] name = "thiserror" version = "2.0.17" @@ -2356,6 +2964,16 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -2367,6 +2985,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -2382,7 +3028,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap", + "indexmap 2.12.0", "toml_datetime", "toml_parser", "winnow", @@ -2397,6 +3043,12 @@ dependencies = [ "winnow", ] +[[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" @@ -2444,6 +3096,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.19.0" @@ -2536,6 +3194,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[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" @@ -2621,6 +3288,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2698,6 +3387,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2926,6 +3624,16 @@ dependencies = [ "tap", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0b3a6dd..ac5143a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = [ + "apps/container", "public/database", - "public/migration", + "public/migration" ] resolver = "3" diff --git a/apps/container/.gitignore b/apps/container/.gitignore new file mode 100644 index 0000000..3fe20be --- /dev/null +++ b/apps/container/.gitignore @@ -0,0 +1 @@ +generated/ \ No newline at end of file diff --git a/apps/container/Cargo.toml b/apps/container/Cargo.toml new file mode 100644 index 0000000..6b53abc --- /dev/null +++ b/apps/container/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "container-simulate" +version = "0.1.0" +edition = "2024" + +[lib] +name = "container" +path = "src/lib.rs" + +[dependencies] +async-trait = "0.1.89" +testcontainers = "0.24.0" +shared = { path = "../../public/shared" } +tokio = { version = "1.47.0", features = ["full"] } +url = "2.5.7" +clap = { version = "4.5.48", features = ["derive", "env"] } +path-clean = "1.0.1" diff --git a/apps/container/src/db.rs b/apps/container/src/db.rs new file mode 100644 index 0000000..0f0e2bc --- /dev/null +++ b/apps/container/src/db.rs @@ -0,0 +1,59 @@ +pub mod config; +pub mod postgresql; +pub mod sqlite; + +use async_trait::async_trait; +use shared::db_type::DBType; +use std::future::Future; +use std::{pin::Pin, sync::Arc}; +use url::Host; + +use testcontainers::{ContainerAsync, GenericImage, TestcontainersError}; + +use crate::{ConfigInfoType, WithContainer, WithoutContainer}; + +pub type UnStartedContainer = + Pin, TestcontainersError>> + Send>>; + +pub type DBConfigInfoType = ConfigInfoType; + +#[derive(Clone)] +pub struct PreExistingDBInfo { + pub db_type: DBType, + pub url: String, + pub on_delete: Arc, +} + +impl WithoutContainer for PreExistingDBInfo { + fn on_delete(&self) { + (self.on_delete)(); + } +} + +#[derive(Clone)] +pub struct ContainerizedDBInfo { + pub db_type: DBType, + pub container: Arc>, + pub container_name: String, + pub database_name: String, + pub host: Host, + pub port: u16, + pub url: String, + pub user: String, + pub password: String, +} + +impl WithContainer for ContainerizedDBInfo { + fn container(&self) -> &Arc> { + &self.container + } +} + +#[async_trait] +pub trait DBInfo { + async fn new(config: Option) -> Self + where + Self: Sized; + async fn get_db_container_config_info(&self) -> DBConfigInfoType; + fn get_unstarted_container(&self) -> Result; +} diff --git a/apps/container/src/db/config.rs b/apps/container/src/db/config.rs new file mode 100644 index 0000000..7d759c7 --- /dev/null +++ b/apps/container/src/db/config.rs @@ -0,0 +1,53 @@ +pub struct OptionalContainerConfig { + pub image: Option, + pub tag: Option, + pub container_name: Option, + pub database_name: Option, + pub user: Option, + pub password: Option, +} + +#[derive(Clone)] +pub struct ContainerConfig { + pub image: String, + pub tag: String, + pub container_name: String, + pub database_name: String, + pub user: String, + pub password: String, +} + +impl OptionalContainerConfig { + pub fn fill_with(&self, other: &ContainerConfig) -> ContainerConfig { + ContainerConfig { + image: self.image.clone().unwrap_or_else(|| other.image.clone()), + tag: self.tag.clone().unwrap_or_else(|| other.tag.clone()), + container_name: self + .container_name + .clone() + .unwrap_or_else(|| other.container_name.clone()), + database_name: self + .database_name + .clone() + .unwrap_or_else(|| other.database_name.clone()), + user: self.user.clone().unwrap_or_else(|| other.user.clone()), + password: self + .password + .clone() + .unwrap_or_else(|| other.password.clone()), + } + } +} + +impl Default for OptionalContainerConfig { + fn default() -> Self { + Self { + image: None, + tag: None, + container_name: None, + database_name: None, + user: None, + password: None, + } + } +} diff --git a/apps/container/src/db/postgresql.rs b/apps/container/src/db/postgresql.rs new file mode 100644 index 0000000..9069b9b --- /dev/null +++ b/apps/container/src/db/postgresql.rs @@ -0,0 +1,95 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use testcontainers::{ + GenericImage, ImageExt, + core::{IntoContainerPort, WaitFor}, + runners::AsyncRunner, +}; + +use crate::{ + ConfigInfoType, + db::{ + ContainerizedDBInfo, DBConfigInfoType, DBInfo, UnStartedContainer, + config::{ContainerConfig, OptionalContainerConfig}, + }, +}; + +pub fn get_default_config() -> ContainerConfig { + ContainerConfig { + container_name: "yanpm-postgres".to_string(), + database_name: "postgres".to_string(), + user: "postgres".to_string(), + password: "postgres".to_string(), + image: "postgres".to_string(), + tag: "16-alpine".to_string(), + } +} + +pub struct PostgreSQLContainer { + pub config: ContainerConfig, +} + +#[async_trait] +impl DBInfo for PostgreSQLContainer { + async fn get_db_container_config_info(&self) -> DBConfigInfoType { + let pg_container = self + .get_unstarted_container() + .unwrap() + .await + .expect("Failed to start PostgreSQL container"); + let pg_host = pg_container.get_host().await.expect("Failed to get host"); + let pg_host_port = pg_container + .get_host_port_ipv4(5432.tcp()) + .await + .expect("Failed to get host port"); + let pg_url = format!( + "postgres://{}:{}@{}:{}/{}", + self.config.user, + self.config.password, + pg_host, + pg_host_port, + self.config.database_name + ); + + ConfigInfoType::Containerized(ContainerizedDBInfo { + db_type: crate::db::DBType::PostgreSQL, + container: Arc::new(pg_container), + container_name: self.config.container_name.clone(), + database_name: self.config.database_name.clone(), + host: pg_host, + port: pg_host_port, + url: pg_url, + user: self.config.user.clone(), + password: self.config.password.clone(), + }) + } + + async fn new(user_default_config: Option) -> Self + where + Self: Sized, + { + let default_config = get_default_config(); + let config = user_default_config + .unwrap_or_default() + .fill_with(&default_config); + + PostgreSQLContainer { + config: config.clone(), + } + } + + fn get_unstarted_container(&self) -> Result { + Ok( + GenericImage::new(self.config.image.clone(), self.config.tag.clone()) + .with_exposed_port(5432.tcp()) + .with_wait_for(WaitFor::message_on_stderr( + "database system is ready to accept connections", + )) + .with_container_name(self.config.container_name.clone()) + .with_env_var("POSTGRES_USER", self.config.user.clone()) + .with_env_var("POSTGRES_PASSWORD", self.config.password.clone()) + .start(), + ) + } +} diff --git a/apps/container/src/db/sqlite.rs b/apps/container/src/db/sqlite.rs new file mode 100644 index 0000000..b7ceb55 --- /dev/null +++ b/apps/container/src/db/sqlite.rs @@ -0,0 +1,113 @@ +use std::{path::PathBuf, sync::Arc}; + +use async_trait::async_trait; + +use crate::{ + ConfigInfoType, + db::{DBConfigInfoType, DBInfo, PreExistingDBInfo, UnStartedContainer}, + util::to_absolute_path, +}; + +#[derive(Clone)] +pub struct ContainerConfig { + // Add any SQLite-specific configuration options here if needed + pub database_name: String, + pub absolute_dir_path: PathBuf, +} + +pub struct OptionalContainerConfig { + // Add any optional configuration fields here + pub database_name: Option, + pub absolute_path: Option, +} + +impl OptionalContainerConfig { + pub fn fill_with(&self, other: &ContainerConfig) -> ContainerConfig { + ContainerConfig { + database_name: self + .database_name + .clone() + .unwrap_or_else(|| other.database_name.clone()), + absolute_dir_path: self + .absolute_path + .clone() + .unwrap_or_else(|| other.absolute_dir_path.clone()), + } + } +} + +impl Default for OptionalContainerConfig { + fn default() -> Self { + Self { + database_name: None, + absolute_path: None, + } + } +} + +pub fn get_default_config() -> ContainerConfig { + ContainerConfig { + database_name: "sqlite".to_string(), + absolute_dir_path: to_absolute_path("./generated/sqlite"), + } +} + +pub struct SQLiteContainer { + pub config: ContainerConfig, +} + +impl SQLiteContainer { + fn get_db_absolute_path(&self) -> PathBuf { + self.config + .absolute_dir_path + .join(&self.config.database_name) + .with_extension("db") + } +} + +#[async_trait] +impl DBInfo for SQLiteContainer { + async fn get_db_container_config_info(&self) -> DBConfigInfoType { + // sqlite filepath url does not include the "sqlite://" prefix + let sqlite_url = format!("{}", self.get_db_absolute_path().to_string_lossy()); + // create the file + std::fs::create_dir_all(&self.config.absolute_dir_path) + .expect("Failed to create directories for SQLite database"); + std::fs::File::create(self.get_db_absolute_path()) + .expect("Failed to create SQLite database file"); + // + ConfigInfoType::PreExisting(PreExistingDBInfo { + db_type: crate::db::DBType::SQLite, + url: sqlite_url, + on_delete: { + let db_path = self.get_db_absolute_path(); + Arc::new(move || { + // delete the sqlite database file + if db_path.exists() { + if let Err(e) = std::fs::remove_file(&db_path) { + eprintln!("Failed to delete SQLite database file: {}", e); + } + } + }) + }, + }) + } + + async fn new(user_default_config: Option) -> Self + where + Self: Sized, + { + let default_config = get_default_config(); + let config = user_default_config + .unwrap_or_default() + .fill_with(&default_config); + + SQLiteContainer { + config: config.clone(), + } + } + + fn get_unstarted_container(&self) -> Result { + Err(()) + } +} diff --git a/apps/container/src/env.rs b/apps/container/src/env.rs new file mode 100644 index 0000000..ec1822c --- /dev/null +++ b/apps/container/src/env.rs @@ -0,0 +1,36 @@ +use std::io::Write; + +use shared::db_type::DBType; + +#[derive(Clone, Copy)] +pub enum EnvFileType { + DotEnv, + Yaml, +} + +#[derive(Clone)] +pub struct EnvFile { + pub file_type: EnvFileType, + pub db_type: DBType, + pub db_url: String, +} + +impl EnvFile { + pub fn write(self, path: impl AsRef) { + let path_ref = path.as_ref(); + println!("Config file path: {}", path_ref.display()); + let mut config_file = + std::fs::File::create(path_ref).expect("Failed to create config file"); + // + self._write_line(&mut config_file, "DB_TYPE", &self.db_type.to_string()); + self._write_line(&mut config_file, "DATABASE_URL", &self.db_url.to_string()) + } + + fn _write_line(&self, file: &mut std::fs::File, key: &str, value: &str) { + match self.file_type { + EnvFileType::DotEnv => writeln!(file, "{}={}", key, value), + EnvFileType::Yaml => writeln!(file, "{}: \"{}\"", key, value), + } + .expect("Failed to write to config file"); + } +} diff --git a/apps/container/src/lib.rs b/apps/container/src/lib.rs new file mode 100644 index 0000000..b3a744a --- /dev/null +++ b/apps/container/src/lib.rs @@ -0,0 +1,95 @@ +pub mod db; +mod env; +pub mod types; +mod util; + +use crate::{ + db::DBConfigInfoType, + types::{ConfigInfoType, WithContainer, WithoutContainer}, + util::{ + await_termination_signal, remove_file_if_exists, stop_container, to_absolute_path, + write_env_files, + }, +}; + +#[derive(Clone)] +pub struct Config { + pub database: DBConfigInfoType, +} + +// relative to the pwd +const API_CONFIG_PATH: &str = "../api/generated-config.yaml"; +const DB_CONFIG_PATH: &str = "../../public/database/.env.generated"; + +pub struct DetachedHandle<'a> { + stopped: bool, + pub config: &'a Config, +} + +impl<'a> DetachedHandle<'a> { + pub fn new(config: &'a Config) -> Self { + DetachedHandle { + stopped: false, + config, + } + } + + pub async fn stop(&mut self) { + if self.stopped { + eprintln!("Attempted to stop an already stopped DetachedHandle."); + return; + } + self.stopped = true; + stop(self.config).await; + } +} + +impl<'a> Drop for DetachedHandle<'a> { + fn drop(&mut self) { + if self.stopped { + return; + } + eprintln!( + "Warning: DetachedHandle was dropped without calling stop(). The container may still be running." + ); + } +} + +async fn start(config: &Config) { + let db_config = &config.database; + // + // write the config files for the api server and database client + println!("Writing config files..."); + write_env_files(&db_config); + println!("Config files written to:"); + println!(" - {}", to_absolute_path(API_CONFIG_PATH).display()); + println!(" - {}", to_absolute_path(DB_CONFIG_PATH).display()); +} + +async fn stop(config: &Config) { + let db_config = &config.database; + // stop the container + println!("Stopping container..."); + stop_container(db_config, "database".to_string()).await; + // remove the generated config file + println!("Removing generated config file..."); + remove_file_if_exists(DB_CONFIG_PATH); + remove_file_if_exists(API_CONFIG_PATH); + println!("Container stopped."); +} + +pub async fn start_attached(config: &Config) { + start(config).await; + + // wait for user input, ctrl+c, or other signals before exiting + println!("Press Ctrl+C, or send SIGTERM to stop the container..."); + await_termination_signal().await; + + stop(config).await; +} + +#[must_use] +pub async fn start_detached(config: &'_ Config) -> DetachedHandle<'_> { + start(config).await; + DetachedHandle::new(config) +} diff --git a/apps/container/src/main.rs b/apps/container/src/main.rs new file mode 100644 index 0000000..b4e38c3 --- /dev/null +++ b/apps/container/src/main.rs @@ -0,0 +1,53 @@ +use clap::Parser; +use container::Config; +use container::start_attached; + +use container::db::DBInfo; + +/// Command line arguments +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Database type to use: 'postgres' or 'sqlite'. Can also be set with DB_TYPE env var. + #[arg(long, default_value = "sqlite", env = "DB_TYPE")] + db_type: String, +} + +#[tokio::main] +async fn main() { + // Parse command line arguments and environment variables + let args = Args::parse(); + + println!("Starting container with database type: {}", args.db_type); + let db_config = match args.db_type.to_lowercase().as_str() { + "postgres" | "pg" | "pgsql" => { + use container::db::postgresql::PostgreSQLContainer; + println!("Using PostgreSQL database"); + PostgreSQLContainer::new(None) + .await + .get_db_container_config_info() + .await + } + "sqlite" | "sql" => { + println!("Using SQLite database"); + use container::db::sqlite::SQLiteContainer; + SQLiteContainer::new(None) + .await + .get_db_container_config_info() + .await + } + other => { + eprintln!("Unknown db_type: {}. Use 'postgres' or 'sqlite'", other); + std::process::exit(1); + } + }; + println!("Database configuration obtained."); + + let config = Config { + database: db_config, + }; + + println!("Starting container..."); + start_attached(&config).await; + println!("Container stopped. Exiting..."); +} diff --git a/apps/container/src/types.rs b/apps/container/src/types.rs new file mode 100644 index 0000000..d91a7e6 --- /dev/null +++ b/apps/container/src/types.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use testcontainers::{ContainerAsync, GenericImage}; + +pub trait WithContainer { + fn container(&self) -> &Arc>; +} + +pub trait WithoutContainer { + fn on_delete(&self); +} + +#[derive(Clone)] +pub enum ConfigInfoType +where + T: WithContainer, + U: WithoutContainer, +{ + Containerized(T), + PreExisting(U), +} diff --git a/apps/container/src/util.rs b/apps/container/src/util.rs new file mode 100644 index 0000000..7fa295f --- /dev/null +++ b/apps/container/src/util.rs @@ -0,0 +1,114 @@ +use path_clean::PathClean; +use std::path::{Path, PathBuf}; +use tokio::signal::unix::{SignalKind, signal}; + +use crate::{ + API_CONFIG_PATH, DB_CONFIG_PATH, + db::DBConfigInfoType, + env::{self, EnvFile}, + types::{ConfigInfoType, WithContainer, WithoutContainer}, +}; + +// relative to the current working directory +pub fn to_absolute_path(path: &str) -> PathBuf { + if Path::new(path).is_absolute() { + return PathBuf::from(path); + } + std::env::current_dir() + .map(|cwd| cwd.join(path)) + .unwrap_or_else(|_| PathBuf::from(path)) + .clean() +} + +pub fn write_env_files(db_config: &DBConfigInfoType) { + let api_config_path_absolute = to_absolute_path(API_CONFIG_PATH); + let db_config_path_absolute = to_absolute_path(DB_CONFIG_PATH); + + let (db_type, db_url) = match db_config { + DBConfigInfoType::Containerized(config) => (config.db_type.clone(), config.url.clone()), + DBConfigInfoType::PreExisting(config) => (config.db_type.clone(), config.url.clone()), + }; + + let api_env_file = EnvFile { + file_type: env::EnvFileType::Yaml, + db_type: db_type, + db_url: db_url, + }; + + let mut db_env_file = api_env_file.clone(); + db_env_file.file_type = env::EnvFileType::DotEnv; + + api_env_file.write(&api_config_path_absolute); + db_env_file.write(&db_config_path_absolute); +} + +pub async fn stop_container( + config: &ConfigInfoType, + config_name: String, +) { + match config { + ConfigInfoType::Containerized(container_info) => { + let container = container_info.container(); + if let Err(e) = container.stop().await { + eprintln!( + "Failed to stop container: {}. With error: {}", + config_name, e + ); + } else { + println!("Container {} stopped successfully.", config_name); + } + } + ConfigInfoType::PreExisting(pre_existing_info) => { + pre_existing_info.on_delete(); + println!( + "Pre-existing resource for {} cleaned up successfully.", + config_name + ); + } + } +} + +pub fn remove_file_if_exists(path: &str) { + let path = std::path::Path::new(path); + if path.exists() { + if let Err(e) = std::fs::remove_file(path) { + eprintln!("Failed to remove file {}: {}", path.display(), e); + } else { + println!("Removed existing file: {}", path.display()); + } + } +} + +pub async fn await_termination_signal() { + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!("\nReceived Ctrl+C, stopping container..."); + } + _ = async { + #[cfg(unix)] + { + let mut sigterm = signal(SignalKind::terminate()).expect("Failed to register SIGTERM handler"); + sigterm.recv().await; + println!("\nReceived SIGTERM, stopping container..."); + } + #[cfg(not(unix))] + { + // On non-Unix systems, just wait indefinitely + std::future::pending::<()>().await; + } + } => {} + _ = async { + #[cfg(unix)] + { + let mut sigquit = signal(SignalKind::quit()).expect("Failed to register SIGQUIT handler"); + sigquit.recv().await; + println!("\nReceived SIGQUIT, stopping container..."); + } + #[cfg(not(unix))] + { + // On non-Unix systems, just wait indefinitely + std::future::pending::<()>().await; + } + } => {} + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..22fac2b --- /dev/null +++ b/justfile @@ -0,0 +1,7 @@ +simulate *args: + cd src/container && \ + if [ -n "{{args}}" ]; then \ + cargo run --bin container-simulate -- --db-type={{args}}; \ + else \ + cargo run --bin container-simulate; \ + fi