diff options
author | Ava Hahn <a.hahn@f5.com> | 2024-04-25 20:02:26 -0700 |
---|---|---|
committer | avahahn <110854134+avahahn@users.noreply.github.com> | 2024-05-08 13:30:08 -0700 |
commit | 6e8f7bbb91e7069d82abd22fbe8d0fcaa1bb2f8c (patch) | |
tree | c4a72ffc97128e5cc2cdc89d58d593cc5914f0cc | |
parent | 5d1ce5c4475f8d126b61650f3c678e3f94549f6b (diff) | |
download | unit-6e8f7bbb91e7069d82abd22fbe8d0fcaa1bb2f8c.tar.gz unit-6e8f7bbb91e7069d82abd22fbe8d0fcaa1bb2f8c.tar.bz2 |
tools/unitctl: Initial Docker Procedures
* move UnitdProcess serialization logic into UnitdProcess
* filter out docker processes from process output on Linux
* initial implementation of a UnitdContainer type
* initial implementation of a docker container search for unitd
* pull out custom openapi future executor and use same tokio
runtime as docker client
* refactor openapi client to not manage its own tokio runtime
* process mount points per docker container
* correctly output docker container info in relevant unitd
instances
* create UnitdProcess from UnitdContainer
* UnitdProcess now owns UnitdContainer
* get and parse container details from docker API
* introduce procedure to rewrite file paths based on docker
container mounts
* test path rewrite facilities
* apply path rewrite to unix socket
Signed-off-by: Ava Hahn <a.hahn@f5.com>
-rw-r--r-- | tools/unitctl/Cargo.lock | 474 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/Cargo.toml | 2 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/lib.rs | 1 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unit_client.rs | 146 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unitd_cmd.rs | 5 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unitd_docker.rs | 282 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unitd_instance.rs | 77 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unitd_process.rs | 34 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/edit.rs | 7 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/execute.rs | 17 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/import.rs | 50 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/instances.rs | 4 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/listeners.rs | 5 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/cmd/status.rs | 5 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/main.rs | 15 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/requests.rs | 17 | ||||
-rw-r--r-- | tools/unitctl/unitctl/src/wait.rs | 34 |
17 files changed, 990 insertions, 185 deletions
diff --git a/tools/unitctl/Cargo.lock b/tools/unitctl/Cargo.lock index 2940b3ae..2acbfb9a 100644 --- a/tools/unitctl/Cargo.lock +++ b/tools/unitctl/Cargo.lock @@ -27,6 +27,21 @@ dependencies = [ ] [[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] name = "anstream" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -129,6 +144,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -173,6 +194,56 @@ dependencies = [ ] [[package]] +name = "bollard" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" +dependencies = [ + "base64 0.22.0", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http 1.1.0", + "http-body-util", + "hyper 1.3.1", + "hyper-named-pipe", + "hyper-util", + "hyperlocal-next", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.44.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] name = "bytes" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -205,6 +276,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.0", +] + +[[package]] name = "clang-sys" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -356,6 +440,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" [[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -378,6 +472,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -569,6 +669,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -610,13 +716,47 @@ dependencies = [ ] [[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] name = "http-body" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.8", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -642,8 +782,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.8", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -656,19 +796,73 @@ dependencies = [ ] [[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "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 1.3.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] name = "hyper-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.27", "native-tls", "tokio", "tokio-native-tls", ] [[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] name = "hyperlocal" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -676,12 +870,50 @@ checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", "hex", - "hyper", + "hyper 0.14.27", "pin-project", "tokio", ] [[package]] +name = "hyperlocal-next" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -698,7 +930,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", ] [[package]] @@ -726,6 +970,15 @@ dependencies = [ ] [[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] name = "json5" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -884,6 +1137,12 @@ dependencies = [ ] [[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1056,6 +1315,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1238,7 +1503,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ - "base64", + "base64 0.21.5", "rustls-pki-types", ] @@ -1316,22 +1581,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.103", + "syn 2.0.60", ] [[package]] @@ -1340,19 +1605,59 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ - "indexmap", + "indexmap 1.9.1", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] +name = "serde_with" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0" +dependencies = [ + "base64 0.22.0", + "chrono", + "hex", + "indexmap 1.9.1", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "time", +] + +[[package]] name = "serde_yaml" version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" dependencies = [ - "indexmap", + "indexmap 1.9.1", "itoa", "ryu", "serde", @@ -1386,6 +1691,12 @@ dependencies = [ ] [[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] name = "socket2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1494,6 +1805,37 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1515,6 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", + "bytes", "libc", "mio", "num_cpus", @@ -1546,6 +1889,42 @@ dependencies = [ ] [[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1558,6 +1937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-core", ] @@ -1614,13 +1994,15 @@ dependencies = [ name = "unit-client-rs" version = "0.4.0-beta" dependencies = [ + "bollard", "custom_error", "futures", "hex", - "hyper", + "hyper 0.14.27", "hyper-tls", "hyperlocal", "rand", + "regex", "rustls", "serde", "serde_json", @@ -1634,10 +2016,10 @@ dependencies = [ name = "unit-openapi" version = "0.4.0-beta" dependencies = [ - "base64", + "base64 0.21.5", "futures", - "http", - "hyper", + "http 0.2.8", + "hyper 0.14.27", "serde", "serde_derive", "serde_json", @@ -1652,7 +2034,7 @@ dependencies = [ "colored_json", "custom_error", "futures", - "hyper", + "hyper 0.14.27", "hyper-tls", "hyperlocal", "json5", @@ -1736,6 +2118,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/tools/unitctl/unit-client-rs/Cargo.toml b/tools/unitctl/unit-client-rs/Cargo.toml index d3b2f9cf..3e48ee23 100644 --- a/tools/unitctl/unit-client-rs/Cargo.toml +++ b/tools/unitctl/unit-client-rs/Cargo.toml @@ -27,6 +27,8 @@ which = "5.0" unit-openapi = { path = "../unit-openapi" } rustls = "0.23.5" +bollard = "0.16.1" +regex = "1.10.4" [dev-dependencies] rand = "0.8.5" diff --git a/tools/unitctl/unit-client-rs/src/lib.rs b/tools/unitctl/unit-client-rs/src/lib.rs index dca8a86f..a0933f42 100644 --- a/tools/unitctl/unit-client-rs/src/lib.rs +++ b/tools/unitctl/unit-client-rs/src/lib.rs @@ -10,6 +10,7 @@ mod runtime_flags; pub mod unit_client; mod unitd_cmd; pub mod unitd_configure_options; +pub mod unitd_docker; pub mod unitd_instance; pub mod unitd_process; mod unitd_process_user; diff --git a/tools/unitctl/unit-client-rs/src/unit_client.rs b/tools/unitctl/unit-client-rs/src/unit_client.rs index b856fd20..7456b106 100644 --- a/tools/unitctl/unit-client-rs/src/unit_client.rs +++ b/tools/unitctl/unit-client-rs/src/unit_client.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::error::Error as StdError; use std::fmt::Debug; -use std::future::Future; use std::rc::Rc; use std::{fmt, io}; @@ -13,7 +12,6 @@ use hyper::{http, Body, Client, Request}; use hyper_tls::HttpsConnector; use hyperlocal::{UnixClientExt, UnixConnector}; use serde::{Deserialize, Serialize}; -use tokio::runtime::Runtime; use crate::control_socket_address::ControlSocket; use unit_openapi::apis::configuration::Configuration; @@ -168,51 +166,38 @@ where #[derive(Debug)] pub struct UnitClient { pub control_socket: ControlSocket, - /// A `current_thread` runtime for executing operations on the - /// asynchronous client in a blocking manner. - rt: Runtime, /// Client for communicating with the control API over the UNIX domain socket client: Box<RemoteClient<Body>>, } impl UnitClient { - pub fn new_with_runtime(control_socket: ControlSocket, runtime: Runtime) -> Self { + pub fn new(control_socket: ControlSocket) -> Self { if control_socket.is_local_socket() { - Self::new_unix(control_socket, runtime) + Self::new_unix(control_socket) } else { - Self::new_http(control_socket, runtime) + Self::new_http(control_socket) } } - pub fn new(control_socket: ControlSocket) -> Self { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("Unable to create a current_thread runtime"); - Self::new_with_runtime(control_socket, runtime) - } - - pub fn new_http(control_socket: ControlSocket, runtime: Runtime) -> Self { + pub fn new_http(control_socket: ControlSocket) -> Self { let remote_client = Client::builder().build(HttpsConnector::new()); Self { control_socket, - rt: runtime, client: Box::from(RemoteClient::Tcp { client: remote_client }), } } - pub fn new_unix(control_socket: ControlSocket, runtime: Runtime) -> UnitClient { + pub fn new_unix(control_socket: ControlSocket) -> UnitClient { let remote_client = Client::unix(); Self { control_socket, - rt: runtime, client: Box::from(RemoteClient::Unix { client: remote_client }), } } /// Sends a request to UNIT and deserializes the JSON response body into the value of type `RESPONSE`. - pub fn send_request_and_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( + pub async fn send_request_and_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( &self, mut request: Request<Body>, ) -> Result<RESPONSE, UnitClientError> { @@ -223,34 +208,32 @@ impl UnitClient { let response_future = self.client.request(request); - self.rt.block_on(async { - let response = response_future - .await - .map_err(|error| UnitClientError::new(error, self.control_socket.to_string(), path.to_string()))?; - - let status = response.status(); - let body = hyper::body::aggregate(response) - .await - .map_err(|error| UnitClientError::new(error, self.control_socket.to_string(), path.to_string()))?; - let reader = &mut body.reader(); - if !status.is_success() { - let error: HashMap<String, String> = - serde_json::from_reader(reader).map_err(|error| UnitClientError::JsonError { - source: error, - path: path.to_string(), - })?; - - return Err(UnitClientError::HttpResponseJsonBodyError { - status, + let response = response_future + .await + .map_err(|error| UnitClientError::new(error, self.control_socket.to_string(), path.to_string()))?; + + let status = response.status(); + let body = hyper::body::aggregate(response) + .await + .map_err(|error| UnitClientError::new(error, self.control_socket.to_string(), path.to_string()))?; + let reader = &mut body.reader(); + if !status.is_success() { + let error: HashMap<String, String> = + serde_json::from_reader(reader).map_err(|error| UnitClientError::JsonError { + source: error, path: path.to_string(), - error: error.get("error").unwrap_or(&"Unknown error".into()).to_string(), - detail: error.get("detail").unwrap_or(&"".into()).to_string(), - }); - } - serde_json::from_reader(reader).map_err(|error| UnitClientError::JsonError { - source: error, + })?; + + return Err(UnitClientError::HttpResponseJsonBodyError { + status, path: path.to_string(), - }) + error: error.get("error").unwrap_or(&"Unknown error".into()).to_string(), + detail: error.get("detail").unwrap_or(&"".into()).to_string(), + }); + } + serde_json::from_reader(reader).map_err(|error| UnitClientError::JsonError { + source: error, + path: path.to_string(), }) } @@ -258,23 +241,17 @@ impl UnitClient { new_openapi_client!(self, ListenersApiClient, ListenersApi) } - pub fn listeners(&self) -> Result<HashMap<String, ConfigListener>, Box<UnitClientError>> { - let list_listeners = self.listeners_api().get_listeners(); - self.execute_openapi_future(list_listeners) - } - - pub fn execute_openapi_future<F: Future<Output = Result<R, OpenAPIError>>, R: for<'de> serde::Deserialize<'de>>( - &self, - future: F, - ) -> Result<R, Box<UnitClientError>> { - self.rt.block_on(future).map_err(|error| { - let remapped_error = if let OpenAPIError::Hyper(hyper_error) = error { - UnitClientError::new(hyper_error, self.control_socket.to_string(), "".to_string()) + pub async fn listeners(&self) -> Result<HashMap<String, ConfigListener>, Box<UnitClientError>> { + self.listeners_api().get_listeners().await.or_else(|err| { + if let OpenAPIError::Hyper(hyper_error) = err { + Err(Box::new(UnitClientError::new( + hyper_error, + self.control_socket.to_string(), + "".to_string(), + ))) } else { - UnitClientError::OpenAPIError { source: error } - }; - - Box::new(remapped_error) + Err(Box::new(UnitClientError::OpenAPIError { source: err })) + } }) } @@ -282,13 +259,22 @@ impl UnitClient { new_openapi_client!(self, StatusApiClient, StatusApi) } - pub fn status(&self) -> Result<Status, Box<UnitClientError>> { - let status = self.status_api().get_status(); - self.execute_openapi_future(status) + pub async fn status(&self) -> Result<Status, Box<UnitClientError>> { + self.status_api().get_status().await.or_else(|err| { + if let OpenAPIError::Hyper(hyper_error) = err { + Err(Box::new(UnitClientError::new( + hyper_error, + self.control_socket.to_string(), + "".to_string(), + ))) + } else { + Err(Box::new(UnitClientError::OpenAPIError { source: err })) + } + }) } - pub fn is_running(&self) -> bool { - self.status().is_ok() + pub async fn is_running(&self) -> bool { + self.status().await.is_ok() } } @@ -336,9 +322,9 @@ mod tests { use super::*; // Integration tests - #[test] - fn can_connect_to_unit_api() { - match UnitdInstance::running_unitd_instances().first() { + #[tokio::test] + async fn can_connect_to_unit_api() { + match UnitdInstance::running_unitd_instances().await.first() { Some(unit_instance) => { let control_api_socket_address = unit_instance .control_api_socket_address() @@ -346,7 +332,7 @@ mod tests { let control_socket = ControlSocket::try_from(control_api_socket_address) .expect("Unable to parse control socket address"); let unit_client = UnitClient::new(control_socket); - assert!(unit_client.is_running()); + assert!(unit_client.is_running().await); } None => { eprintln!("No running unitd instances found - skipping test"); @@ -354,9 +340,9 @@ mod tests { } } - #[test] - fn can_get_unit_status() { - match UnitdInstance::running_unitd_instances().first() { + #[tokio::test] + async fn can_get_unit_status() { + match UnitdInstance::running_unitd_instances().await.first() { Some(unit_instance) => { let control_api_socket_address = unit_instance .control_api_socket_address() @@ -364,7 +350,7 @@ mod tests { let control_socket = ControlSocket::try_from(control_api_socket_address) .expect("Unable to parse control socket address"); let unit_client = UnitClient::new(control_socket); - let status = unit_client.status().expect("Unable to get unit status"); + let status = unit_client.status().await.expect("Unable to get unit status"); println!("Unit status: {:?}", status); } None => { @@ -373,9 +359,9 @@ mod tests { } } - #[test] - fn can_get_unit_listeners() { - match UnitdInstance::running_unitd_instances().first() { + #[tokio::test] + async fn can_get_unit_listeners() { + match UnitdInstance::running_unitd_instances().await.first() { Some(unit_instance) => { let control_api_socket_address = unit_instance .control_api_socket_address() @@ -383,7 +369,7 @@ mod tests { let control_socket = ControlSocket::try_from(control_api_socket_address) .expect("Unable to parse control socket address"); let unit_client = UnitClient::new(control_socket); - unit_client.listeners().expect("Unable to get Unit listeners"); + unit_client.listeners().await.expect("Unable to get Unit listeners"); } None => { eprintln!("No running unitd instances found - skipping test"); diff --git a/tools/unitctl/unit-client-rs/src/unitd_cmd.rs b/tools/unitctl/unit-client-rs/src/unitd_cmd.rs index c4883ed5..17563cb0 100644 --- a/tools/unitctl/unit-client-rs/src/unitd_cmd.rs +++ b/tools/unitctl/unit-client-rs/src/unitd_cmd.rs @@ -28,11 +28,13 @@ impl UnitdCmd { .expect("Unable to parse cmd") .splitn(2, " [") .collect::<Vec<&str>>(); + if parts.len() != 2 { let msg = format!("cmd does not have the expected format: {}", process_cmd); return Err(IoError::new(ErrorKind::InvalidInput, msg).into()); } - let version: Option<String> = Some(parts[0].to_string()); + + let version = Some(parts[0].to_string()); let executable_path = UnitdCmd::parse_executable_path_from_cmd(parts[1], binary_name); let flags = UnitdCmd::parse_runtime_flags_from_cmd(parts[1]); @@ -69,6 +71,7 @@ impl UnitdCmd { if cmd.is_empty() { return None; } + // Split out everything in between the brackets [ and ] let split = cmd.trim_end_matches(']').splitn(2, '[').collect::<Vec<&str>>(); if split.is_empty() { diff --git a/tools/unitctl/unit-client-rs/src/unitd_docker.rs b/tools/unitctl/unit-client-rs/src/unitd_docker.rs new file mode 100644 index 00000000..d5028afc --- /dev/null +++ b/tools/unitctl/unit-client-rs/src/unitd_docker.rs @@ -0,0 +1,282 @@ +use std::collections::HashMap; +use std::fs::read_to_string; +use std::path::PathBuf; + +use crate::unitd_process::UnitdProcess; + +use bollard::secret::ContainerInspectResponse; +use regex::Regex; +use serde::ser::SerializeMap; +use serde::{Serialize, Serializer}; + +use bollard::{models::ContainerSummary, Docker}; + +#[derive(Clone, Debug)] +pub struct UnitdContainer { + pub container_id: Option<String>, + pub container_image: String, + pub command: Option<String>, + pub mounts: HashMap<PathBuf, PathBuf>, + pub platform: String, + details: Option<ContainerInspectResponse>, +} + +impl From<&ContainerSummary> for UnitdContainer { + fn from(ctr: &ContainerSummary) -> Self { + // we assume paths from the docker api are absolute + // they certainly have to be later... + let mut mounts = HashMap::new(); + if let Some(mts) = &ctr.mounts { + for i in mts { + if let Some(ref src) = i.source { + if let Some(ref dest) = i.destination { + mounts.insert(PathBuf::from(dest.clone()), PathBuf::from(src.clone())); + } + } + } + } + + UnitdContainer { + container_id: ctr.id.clone(), + container_image: format!( + "{} (docker)", + ctr.image.clone().unwrap_or(String::from("unknown container")), + ), + command: ctr.command.clone(), + mounts: mounts, + platform: String::from("Docker"), + details: None, + } + } +} + +impl From<&UnitdContainer> for UnitdProcess { + fn from(ctr: &UnitdContainer) -> Self { + let version = ctr.details.as_ref().and_then(|details| { + details.config.as_ref().and_then(|conf| { + conf.labels.as_ref().and_then(|labels| { + labels + .get("org.opencontainers.image.version") + .and_then(|version| Some(version.clone())) + }) + }) + }); + let command = ctr.command.clone().and_then(|cmd| { + Some(format!( + "{}{} [{}{}]", + "unit: main v", + version.or(Some(String::from(""))).unwrap(), + ctr.container_image, + ctr.rewrite_socket( + cmd.strip_prefix("/usr/local/bin/docker-entrypoint.sh") + .or_else(|| Some("")) + .unwrap() + .to_string()) + )) + }); + let mut cmds = vec![]; + let _ = command.map_or((), |cmd| cmds.push(cmd)); + UnitdProcess { + all_cmds: cmds, + binary_name: ctr.container_image.clone(), + process_id: ctr + .details + .as_ref() + .and_then(|details| { + details + .state + .as_ref() + .and_then(|state| state.pid.and_then(|pid| Some(pid.clone() as u64))) + }) + .or(Some(0 as u64)) + .unwrap(), + executable_path: None, + environ: vec![], + working_dir: ctr.details.as_ref().and_then(|details| { + details.config.as_ref().and_then(|conf| { + Some( + PathBuf::from( + conf.working_dir + .as_ref() + .map_or(String::new(), |dir| ctr.host_path(dir.clone())), + ) + .into_boxed_path(), + ) + }) + }), + child_pids: vec![], + user: None, + effective_user: None, + container: Some(ctr.clone()), + } + } +} + +impl Serialize for UnitdContainer { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut state = serializer.serialize_map(Some(5))?; + state.serialize_entry("container_id", &self.container_id)?; + state.serialize_entry("container_image", &self.container_image)?; + state.serialize_entry("command", &self.command)?; + state.serialize_entry("mounts", &self.mounts)?; + state.serialize_entry("platform", &self.platform)?; + state.end() + } +} + +impl UnitdContainer { + pub async fn find_unitd_containers() -> Vec<UnitdContainer> { + if let Ok(docker) = Docker::connect_with_local_defaults() { + match docker.list_containers::<String>(None).await { + Err(e) => { + eprintln!("{}", e); + vec![] + } + Ok(summary) => { + // cant do this functionally because of the async call + let mut mapped = vec![]; + for ctr in summary { + if ctr.clone().image.or(Some(String::new())).unwrap().contains("unit") { + let mut c = UnitdContainer::from(&ctr); + if let Some(names) = ctr.names { + if names.len() > 0 { + let name = names[0].strip_prefix("/").or(Some(names[0].as_str())).unwrap(); + if let Ok(cir) = docker.inspect_container(name, None).await { + c.details = Some(cir); + } + } + } + mapped.push(c); + } + } + mapped + } + } + } else { + vec![] + } + } + + pub fn host_path(&self, container_path: String) -> String { + let cp = PathBuf::from(container_path); + + // get only possible mount points + // sort to deepest mountpoint first + // assumed deepest possible mount point takes precedence + let mut keys = self + .mounts + .clone() + .into_keys() + .filter(|mp| cp.as_path().starts_with(mp)) + .collect::<Vec<_>>(); + keys.sort_by_key(|a| 0 as isize - a.ancestors().count() as isize); + + // either return translated path or original prefixed with "container" + if keys.len() > 0 { + self.mounts[&keys[0]] + .clone() + .join( + cp.as_path() + .strip_prefix(keys[0].clone()) + .expect("error checking path prefix"), + ) + .to_string_lossy() + .to_string() + } else { + format!("<container>:{}", cp.display()) + } + } + + pub fn rewrite_socket(&self, command: String) -> String { + command + .split(" ") + .map(|tok| if tok.starts_with("unix:") { + format!("unix:{}", self.host_path( + tok.strip_prefix("unix:") + .unwrap() + .to_string())) + } else { + tok.to_string() + }) + .collect::<Vec<_>>() + .join(" ") + } + + pub fn container_is_running(&self) -> Option<bool> { + self.details + .as_ref() + .and_then(|details| details.state.as_ref().and_then(|state| state.running)) + } +} + +/* Returns either 64 char docker container ID or None */ +pub fn pid_is_dockerized(pid: u64) -> bool { + let cg_filepath = format!("/proc/{}/cgroup", pid); + match read_to_string(cg_filepath) { + Err(e) => { + eprintln!("{}", e); + false + } + Ok(contents) => { + let docker_re = Regex::new(r"docker-([a-zA-Z0-9]{64})").unwrap(); + docker_re.is_match(contents.as_str()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_translation() { + let mut mounts = HashMap::new(); + mounts.insert("/1/2/3/4/5/6/7".into(), "/0".into()); + mounts.insert("/root".into(), "/1".into()); + mounts.insert("/root/mid".into(), "/2".into()); + mounts.insert("/root/mid/child".into(), "/3".into()); + mounts.insert("/mid/child".into(), "/4".into()); + mounts.insert("/child".into(), "/5".into()); + + let ctr = UnitdContainer { + container_id: None, + container_image: String::from(""), + command: None, + platform: "test".to_string(), + details: None, + mounts: mounts, + }; + + assert_eq!( + "/3/c2/test".to_string(), + ctr.host_path("/root/mid/child/c2/test".to_string()) + ); + assert_eq!( + "<container>:/path/to/conf".to_string(), + ctr.host_path("/path/to/conf".to_string()) + ); + } + + #[test] + fn test_unix_sock_path_translate() { + let mut mounts = HashMap::new(); + mounts.insert("/var/run".into(), "/tmp".into()); + + let ctr = UnitdContainer { + container_id: None, + container_image: String::from(""), + command: None, + platform: "test".to_string(), + details: None, + mounts: mounts, + }; + + assert_eq!( + ctr.rewrite_socket("unitd --no-daemon --control unix:/var/run/control.unit.sock".to_string()), + "unitd --no-daemon --control unix:/tmp/control.unit.sock".to_string()); + + } +} diff --git a/tools/unitctl/unit-client-rs/src/unitd_instance.rs b/tools/unitctl/unit-client-rs/src/unitd_instance.rs index 9467fcb7..86f8e73d 100644 --- a/tools/unitctl/unit-client-rs/src/unitd_instance.rs +++ b/tools/unitctl/unit-client-rs/src/unitd_instance.rs @@ -1,4 +1,5 @@ use crate::unit_client::UnitClientError; +use crate::unitd_docker::UnitdContainer; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; use std::error::Error as StdError; @@ -25,7 +26,7 @@ impl Serialize for UnitdInstance { where S: Serializer, { - let mut state = serializer.serialize_map(Some(15))?; + let mut state = serializer.serialize_map(Some(11))?; let runtime_flags = self .process .cmd() @@ -34,13 +35,9 @@ impl Serialize for UnitdInstance { let configure_flags = self.configure_options.as_ref().map(|opts| opts.all_flags.clone()); - state.serialize_entry("pid", &self.process.process_id)?; + state.serialize_entry("process", &self.process)?; state.serialize_entry("version", &self.version())?; - state.serialize_entry("user", &self.process.user)?; - state.serialize_entry("effective_user", &self.process.effective_user)?; - state.serialize_entry("executable", &self.process.executable_path())?; state.serialize_entry("control_socket", &self.control_api_socket_address())?; - state.serialize_entry("child_pids", &self.process.child_pids)?; state.serialize_entry("log_path", &self.log_path())?; state.serialize_entry("pid_path", &self.pid_path())?; state.serialize_entry("modules_directory", &self.modules_directory())?; @@ -56,8 +53,19 @@ impl Serialize for UnitdInstance { } impl UnitdInstance { - pub fn running_unitd_instances() -> Vec<UnitdInstance> { - Self::collect_unitd_processes(UnitdProcess::find_unitd_processes()) + pub async fn running_unitd_instances() -> Vec<UnitdInstance> { + Self::collect_unitd_processes( + UnitdProcess::find_unitd_processes() + .into_iter() + .chain( + UnitdContainer::find_unitd_containers() + .await + .into_iter() + .map(|x| UnitdProcess::from(&x)) + .collect::<Vec<_>>(), + ) + .collect(), + ) } /// Find all running unitd processes and convert them into UnitdInstances and filter @@ -91,11 +99,14 @@ impl UnitdInstance { pid: process.process_id, })?; Ok(new_path) - } else { + } else if process.container.is_none() { Err(UnitClientError::UnitdProcessParseError { message: "Unable to get absolute unitd executable path from process".to_string(), pid: process.process_id, }) + } else { + // container case + Ok(PathBuf::from("/").into_boxed_path()) } } None => Err(UnitClientError::UnitdProcessParseError { @@ -107,7 +118,30 @@ impl UnitdInstance { fn map_process_to_unitd_instance(process: &UnitdProcess) -> UnitdInstance { match unitd_path_from_process(process) { - Ok(unitd_path) => match UnitdConfigureOptions::new(&unitd_path.clone().into_path_buf()) { + Ok(_) if process.container.is_some() => { + let mut err = vec![]; + // double check that it is running + let running = process.container + .as_ref() + .unwrap() + .container_is_running(); + + if running.is_none() || !running.unwrap() { + err.push(UnitClientError::UnitdProcessParseError{ + message: "process container is not running".to_string(), + pid: process.process_id, + }); + } + + UnitdInstance { + process: process.to_owned(), + configure_options: None, + errors: err, + } + }, + Ok(unitd_path) => match UnitdConfigureOptions::new( + &unitd_path.clone() + .into_path_buf()) { Ok(configure_options) => UnitdInstance { process: process.to_owned(), configure_options: Some(configure_options), @@ -250,10 +284,22 @@ impl fmt::Display for UnitdInstance { writeln!(f, " API control unix socket: {}", socket_address)?; writeln!(f, " Child processes ids: {}", child_pids)?; writeln!(f, " Runtime flags: {}", runtime_flags)?; - write!(f, " Configure options: {}", configure_flags)?; + writeln!(f, " Configure options: {}", configure_flags)?; + + if let Some(ctr) = &self.process.container { + writeln!(f, " Container:")?; + writeln!(f, " Platform: {}", ctr.platform)?; + if let Some(id) = ctr.container_id.clone() { + writeln!(f, " Container ID: {}", id)?; + } + writeln!(f, " Mounts:")?; + for (k, v) in &ctr.mounts { + writeln!(f, " {} => {}", k.to_string_lossy(), v.to_string_lossy())?; + } + } if !self.errors.is_empty() { - write!(f, "\n Errors:")?; + write!(f, " Errors:")?; for error in &self.errors { write!(f, "\n {}", error)?; } @@ -302,9 +348,9 @@ mod tests { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ]; - #[test] - fn can_find_unitd_instances() { - UnitdInstance::running_unitd_instances().iter().for_each(|p| { + #[tokio::test] + async fn can_find_unitd_instances() { + UnitdInstance::running_unitd_instances().await.iter().for_each(|p| { println!("{:?}", p); println!("Runtime Flags: {:?}", p.process.cmd().map(|c| c.flags)); println!("Temp directory: {:?}", p.tmp_directory()); @@ -326,6 +372,7 @@ mod tests { child_pids: vec![], user: None, effective_user: None, + container: None, } } diff --git a/tools/unitctl/unit-client-rs/src/unitd_process.rs b/tools/unitctl/unit-client-rs/src/unitd_process.rs index b8604e89..2a78bfc6 100644 --- a/tools/unitctl/unit-client-rs/src/unitd_process.rs +++ b/tools/unitctl/unit-client-rs/src/unitd_process.rs @@ -1,6 +1,9 @@ use crate::unitd_cmd::UnitdCmd; +use crate::unitd_docker::{pid_is_dockerized, UnitdContainer}; use crate::unitd_instance::UNITD_BINARY_NAMES; use crate::unitd_process_user::UnitdProcessUser; +use serde::ser::SerializeMap; +use serde::{Serialize, Serializer}; use std::collections::HashMap; use std::path::Path; use sysinfo::{Pid, Process, ProcessRefreshKind, System, UpdateKind, Users}; @@ -16,6 +19,23 @@ pub struct UnitdProcess { pub child_pids: Vec<u64>, pub user: Option<UnitdProcessUser>, pub effective_user: Option<UnitdProcessUser>, + pub container: Option<UnitdContainer>, +} + +impl Serialize for UnitdProcess { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut state = serializer.serialize_map(Some(6))?; + state.serialize_entry("pid", &self.process_id)?; + state.serialize_entry("user", &self.user)?; + state.serialize_entry("effective_user", &self.effective_user)?; + state.serialize_entry("executable", &self.executable_path())?; + state.serialize_entry("child_pids", &self.child_pids)?; + state.serialize_entry("container", &self.container)?; + state.end() + } } impl UnitdProcess { @@ -41,10 +61,15 @@ impl UnitdProcess { .iter() // Filter out child processes .filter(|p| { - let parent_pid = p.1.parent(); - match parent_pid { - Some(pid) => !unitd_processes.contains_key(&pid), - None => false, + #[cfg(target_os = "linux")] + if pid_is_dockerized(p.0.as_u32().into()) { + false + } else { + let parent_pid = p.1.parent(); + match parent_pid { + Some(pid) => !unitd_processes.contains_key(&pid), + None => false, + } } }) .map(|p| { @@ -85,6 +110,7 @@ impl UnitdProcess { child_pids, user, effective_user, + container: None, } }) .collect::<Vec<UnitdProcess>>() diff --git a/tools/unitctl/unitctl/src/cmd/edit.rs b/tools/unitctl/unitctl/src/cmd/edit.rs index cbe01289..21bba519 100644 --- a/tools/unitctl/unitctl/src/cmd/edit.rs +++ b/tools/unitctl/unitctl/src/cmd/edit.rs @@ -18,11 +18,11 @@ const EDITOR_KNOWN_LIST: [&str; 8] = [ "emacs", ]; -pub(crate) fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { - let control_socket = wait::wait_for_socket(cli)?; +pub(crate) async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { + let control_socket = wait::wait_for_socket(cli).await?; let client = UnitClient::new(control_socket); // Get latest configuration - let current_config = send_empty_body_deserialize_response(&client, "GET", "/config")?; + let current_config = send_empty_body_deserialize_response(&client, "GET", "/config").await?; // Write JSON to temporary file - this file will automatically be deleted by the OS when // the last file handle to it is removed. @@ -54,6 +54,7 @@ pub(crate) fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), Unit // Send edited file to UNIT to overwrite current configuration send_and_validate_config_deserialize_response(&client, "PUT", "/config", Some(&inputfile)) + .await .and_then(|status| output_format.write_to_stdout(&status)) } diff --git a/tools/unitctl/unitctl/src/cmd/execute.rs b/tools/unitctl/unitctl/src/cmd/execute.rs index 60957a83..1bde437d 100644 --- a/tools/unitctl/unitctl/src/cmd/execute.rs +++ b/tools/unitctl/unitctl/src/cmd/execute.rs @@ -8,14 +8,14 @@ use crate::wait; use crate::{OutputFormat, UnitctlError}; use unit_client_rs::unit_client::UnitClient; -pub(crate) fn cmd( +pub(crate) async fn cmd( cli: &UnitCtl, output_format: &OutputFormat, input_file: &Option<String>, method: &str, path: &str, ) -> Result<(), UnitctlError> { - let control_socket = wait::wait_for_socket(cli)?; + let control_socket = wait::wait_for_socket(cli).await?; let client = UnitClient::new(control_socket); let path_trimmed = path.trim(); @@ -28,10 +28,10 @@ pub(crate) fn cmd( eprintln!("Cannot use GET method with input file - ignoring input file"); } - send_and_deserialize(client, method_upper, input_file_arg, path_trimmed, output_format) + send_and_deserialize(client, method_upper, input_file_arg, path_trimmed, output_format).await } -fn send_and_deserialize( +async fn send_and_deserialize( client: UnitClient, method: String, input_file: Option<InputFile>, @@ -43,7 +43,8 @@ fn send_and_deserialize( // If we are sending a GET request to a JS modules directory, we want to print the contents of the JS file // instead of the JSON response if method.eq("GET") && is_js_modules_dir && path.ends_with(".js") { - let script = send_body_deserialize_response::<String>(&client, method.as_str(), path, input_file.as_ref())?; + let script = + send_body_deserialize_response::<String>(&client, method.as_str(), path, input_file.as_ref()).await?; println!("{}", script); return Ok(()); } @@ -52,17 +53,17 @@ fn send_and_deserialize( match input_file { Some(input_file) => { if input_file.is_config() { - send_and_validate_config_deserialize_response(&client, method.as_str(), path, Some(&input_file)) + send_and_validate_config_deserialize_response(&client, method.as_str(), path, Some(&input_file)).await // TLS certificate data } else if input_file.is_pem_bundle() { - send_and_validate_pem_data_deserialize_response(&client, method.as_str(), path, &input_file) + send_and_validate_pem_data_deserialize_response(&client, method.as_str(), path, &input_file).await // This is unknown data } else { panic!("Unknown input file type") } } // A none value for an input file can be considered a request to send an empty body - None => send_empty_body_deserialize_response(&client, method.as_str(), path), + None => send_empty_body_deserialize_response(&client, method.as_str(), path).await, } .and_then(|status| output_format.write_to_stdout(&status)) } diff --git a/tools/unitctl/unitctl/src/cmd/import.rs b/tools/unitctl/unitctl/src/cmd/import.rs index e5e57456..81f925bc 100644 --- a/tools/unitctl/unitctl/src/cmd/import.rs +++ b/tools/unitctl/unitctl/src/cmd/import.rs @@ -43,24 +43,25 @@ impl UploadFormat { } } -pub fn cmd(cli: &UnitCtl, directory: &PathBuf) -> Result<(), UnitctlError> { +pub async fn cmd(cli: &UnitCtl, directory: &PathBuf) -> Result<(), UnitctlError> { if !directory.exists() { return Err(UnitctlError::PathNotFound { path: directory.to_string_lossy().into(), }); } - let control_socket = wait::wait_for_socket(cli)?; + let control_socket = wait::wait_for_socket(cli).await?; let client = UnitClient::new(control_socket); - - let results: Vec<Result<(), UnitctlError>> = WalkDir::new(directory) + let mut results = vec![]; + for i in WalkDir::new(directory) .follow_links(true) .sort_by_file_name() .into_iter() .filter_map(Result::ok) .filter(|e| !e.path().is_dir()) - .map(|pe| process_entry(pe, &client)) - .collect(); + { + results.push(process_entry(i, &client).await); + } if results.iter().filter(|r| r.is_err()).count() == results.len() { Err(UnitctlError::NoFilesImported) @@ -70,7 +71,7 @@ pub fn cmd(cli: &UnitCtl, directory: &PathBuf) -> Result<(), UnitctlError> { } } -fn process_entry(entry: DirEntry, client: &UnitClient) -> Result<(), UnitctlError> { +async fn process_entry(entry: DirEntry, client: &UnitClient) -> Result<(), UnitctlError> { let input_file = InputFile::from(entry.path()); if input_file.format() == InputFormat::Unknown { println!( @@ -86,25 +87,34 @@ fn process_entry(entry: DirEntry, client: &UnitClient) -> Result<(), UnitctlErro // We can't overwrite JS or PEM files, so we delete them first if !upload_format.can_be_overwritten() { - let _ = requests::send_empty_body_deserialize_response(client, "DELETE", upload_path.as_str()).ok(); + let _ = requests::send_empty_body_deserialize_response(client, "DELETE", upload_path.as_str()) + .await + .ok(); } let result = match upload_format { - UploadFormat::Config => requests::send_and_validate_config_deserialize_response( - client, - "PUT", - upload_path.as_str(), - Some(&input_file), - ), + UploadFormat::Config => { + requests::send_and_validate_config_deserialize_response( + client, + "PUT", + upload_path.as_str(), + Some(&input_file), + ) + .await + } UploadFormat::PemBundle => { requests::send_and_validate_pem_data_deserialize_response(client, "PUT", upload_path.as_str(), &input_file) + .await + } + UploadFormat::Javascript => { + requests::send_body_deserialize_response::<UnitSerializableMap>( + client, + "PUT", + upload_path.as_str(), + Some(&input_file), + ) + .await } - UploadFormat::Javascript => requests::send_body_deserialize_response::<UnitSerializableMap>( - client, - "PUT", - upload_path.as_str(), - Some(&input_file), - ), }; match result { diff --git a/tools/unitctl/unitctl/src/cmd/instances.rs b/tools/unitctl/unitctl/src/cmd/instances.rs index 26e15027..09e3eb0f 100644 --- a/tools/unitctl/unitctl/src/cmd/instances.rs +++ b/tools/unitctl/unitctl/src/cmd/instances.rs @@ -1,8 +1,8 @@ use crate::{OutputFormat, UnitctlError}; use unit_client_rs::unitd_instance::UnitdInstance; -pub(crate) fn cmd(output_format: OutputFormat) -> Result<(), UnitctlError> { - let instances = UnitdInstance::running_unitd_instances(); +pub(crate) async fn cmd(output_format: OutputFormat) -> Result<(), UnitctlError> { + let instances = UnitdInstance::running_unitd_instances().await; if instances.is_empty() { Err(UnitctlError::NoUnitInstancesError) } else if output_format.eq(&OutputFormat::Text) { diff --git a/tools/unitctl/unitctl/src/cmd/listeners.rs b/tools/unitctl/unitctl/src/cmd/listeners.rs index 081a6cd9..4eb48355 100644 --- a/tools/unitctl/unitctl/src/cmd/listeners.rs +++ b/tools/unitctl/unitctl/src/cmd/listeners.rs @@ -3,11 +3,12 @@ use crate::wait; use crate::{OutputFormat, UnitctlError}; use unit_client_rs::unit_client::UnitClient; -pub fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { - let control_socket = wait::wait_for_socket(cli)?; +pub async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { + let control_socket = wait::wait_for_socket(cli).await?; let client = UnitClient::new(control_socket); client .listeners() + .await .map_err(|e| UnitctlError::UnitClientError { source: *e }) .and_then(|response| output_format.write_to_stdout(&response)) } diff --git a/tools/unitctl/unitctl/src/cmd/status.rs b/tools/unitctl/unitctl/src/cmd/status.rs index 1f40735f..2cac5714 100644 --- a/tools/unitctl/unitctl/src/cmd/status.rs +++ b/tools/unitctl/unitctl/src/cmd/status.rs @@ -3,11 +3,12 @@ use crate::wait; use crate::{OutputFormat, UnitctlError}; use unit_client_rs::unit_client::UnitClient; -pub fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { - let control_socket = wait::wait_for_socket(cli)?; +pub async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> { + let control_socket = wait::wait_for_socket(cli).await?; let client = UnitClient::new(control_socket); client .status() + .await .map_err(|e| UnitctlError::UnitClientError { source: *e }) .and_then(|response| output_format.write_to_stdout(&response)) } diff --git a/tools/unitctl/unitctl/src/main.rs b/tools/unitctl/unitctl/src/main.rs index 2e8cedf1..a52c4ed3 100644 --- a/tools/unitctl/unitctl/src/main.rs +++ b/tools/unitctl/unitctl/src/main.rs @@ -23,26 +23,27 @@ mod unitctl; mod unitctl_error; mod wait; -fn main() -> Result<(), UnitctlError> { +#[tokio::main] +async fn main() -> Result<(), UnitctlError> { let cli = UnitCtl::parse(); match cli.command { - Commands::Instances { output_format } => instances::cmd(output_format), + Commands::Instances { output_format } => instances::cmd(output_format).await, - Commands::Edit { output_format } => edit::cmd(&cli, output_format), + Commands::Edit { output_format } => edit::cmd(&cli, output_format).await, - Commands::Import { ref directory } => import::cmd(&cli, directory), + Commands::Import { ref directory } => import::cmd(&cli, directory).await, Commands::Execute { ref output_format, ref input_file, ref method, ref path, - } => execute_cmd::cmd(&cli, output_format, input_file, method, path), + } => execute_cmd::cmd(&cli, output_format, input_file, method, path).await, - Commands::Status { output_format } => status::cmd(&cli, output_format), + Commands::Status { output_format } => status::cmd(&cli, output_format).await, - Commands::Listeners { output_format } => listeners::cmd(&cli, output_format), + Commands::Listeners { output_format } => listeners::cmd(&cli, output_format).await, } .map_err(|error| { eprint_error(&error); diff --git a/tools/unitctl/unitctl/src/requests.rs b/tools/unitctl/unitctl/src/requests.rs index bd47c645..2743c984 100644 --- a/tools/unitctl/unitctl/src/requests.rs +++ b/tools/unitctl/unitctl/src/requests.rs @@ -12,7 +12,7 @@ use unit_client_rs::unit_client::UnitClientError; /// Send the contents of a file to the unit server /// We assume that the file is valid and can be sent to the server -pub fn send_and_validate_config_deserialize_response( +pub async fn send_and_validate_config_deserialize_response( client: &UnitClient, method: &str, path: &str, @@ -35,20 +35,21 @@ pub fn send_and_validate_config_deserialize_response( let reader = KnownSize::String(json.to_string()); streaming_upload_deserialize_response(client, method, path, mime_type, reader) + .await .map_err(|e| UnitctlError::UnitClientError { source: e }) } /// Send an empty body to the unit server -pub fn send_empty_body_deserialize_response( +pub async fn send_empty_body_deserialize_response( client: &UnitClient, method: &str, path: &str, ) -> Result<UnitSerializableMap, UnitctlError> { - send_body_deserialize_response(client, method, path, None) + send_body_deserialize_response(client, method, path, None).await } /// Send the contents of a PEM file to the unit server -pub fn send_and_validate_pem_data_deserialize_response( +pub async fn send_and_validate_pem_data_deserialize_response( client: &UnitClient, method: &str, path: &str, @@ -65,6 +66,7 @@ pub fn send_and_validate_pem_data_deserialize_response( let known_size = KnownSize::Vec((*bytes).to_owned()); streaming_upload_deserialize_response(client, method, path, Some(input_file.mime_type()), known_size) + .await .map_err(|e| UnitctlError::UnitClientError { source: e }) } @@ -131,7 +133,7 @@ fn validate_pem_items(pem_items: Vec<Result<Item, UnitctlError>>) -> Result<(), Ok(()) } -pub fn send_body_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( +pub async fn send_body_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( client: &UnitClient, method: &str, path: &str, @@ -143,10 +145,11 @@ pub fn send_body_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de> } None => streaming_upload_deserialize_response(client, method, path, None, KnownSize::Empty), } + .await .map_err(|e| UnitctlError::UnitClientError { source: e }) } -fn streaming_upload_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( +async fn streaming_upload_deserialize_response<RESPONSE: for<'de> serde::Deserialize<'de>>( client: &UnitClient, method: &str, path: &str, @@ -171,5 +174,5 @@ fn streaming_upload_deserialize_response<RESPONSE: for<'de> serde::Deserialize<' .insert("Content-Type", content_type.parse().unwrap()); } - client.send_request_and_deserialize_response(request) + client.send_request_and_deserialize_response(request).await } diff --git a/tools/unitctl/unitctl/src/wait.rs b/tools/unitctl/unitctl/src/wait.rs index 998dc59c..313403a8 100644 --- a/tools/unitctl/unitctl/src/wait.rs +++ b/tools/unitctl/unitctl/src/wait.rs @@ -8,10 +8,10 @@ use unit_client_rs::unitd_instance::UnitdInstance; /// Waits for a socket to become available. Availability is tested by attempting to access the /// status endpoint via the control socket. When socket is available, ControlSocket instance /// is returned. -pub fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> { +pub async fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> { // Don't wait, if wait_time is not specified if cli.wait_time_seconds.is_none() { - return cli.control_socket_address.instance_value_if_none().and_validate(); + return cli.control_socket_address.instance_value_if_none().await.and_validate(); } let wait_time = @@ -33,7 +33,7 @@ pub fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> { attempt += 1; - let result = cli.control_socket_address.instance_value_if_none().and_validate(); + let result = cli.control_socket_address.instance_value_if_none().await.and_validate(); if let Err(error) = result { if error.retryable() { @@ -46,7 +46,7 @@ pub fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> { control_socket = result.unwrap(); let client = UnitClient::new(control_socket.clone()); - match client.status() { + match client.status().await { Ok(_) => { return Ok(control_socket.to_owned()); } @@ -65,15 +65,15 @@ pub fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> { } trait OptionControlSocket { - fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError>; + async fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError>; } impl OptionControlSocket for Option<ControlSocket> { - fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError> { + async fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError> { if let Some(control_socket) = self { Ok(control_socket.to_owned()) } else { - find_socket_address_from_instance() + find_socket_address_from_instance().await } } } @@ -109,8 +109,8 @@ impl ResultControlSocket<ControlSocket, UnitctlError> for Result<ControlSocket, } } -fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlError> { - let instances = UnitdInstance::running_unitd_instances(); +async fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlError> { + let instances = UnitdInstance::running_unitd_instances().await; if instances.is_empty() { return Err(UnitctlError::NoUnitInstancesError); } else if instances.len() > 1 { @@ -127,8 +127,8 @@ fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlError> { } } -#[test] -fn wait_for_unavailable_unix_socket() { +#[tokio::test] +async fn wait_for_unavailable_unix_socket() { let control_socket = ControlSocket::try_from("unix:/tmp/this_socket_does_not_exist.sock"); let cli = UnitCtl { control_socket_address: Some(control_socket.unwrap()), @@ -138,15 +138,17 @@ fn wait_for_unavailable_unix_socket() { output_format: crate::output_format::OutputFormat::JsonPretty, }, }; - let error = wait_for_socket(&cli).expect_err("Expected error, but no error received"); + let error = wait_for_socket(&cli) + .await + .expect_err("Expected error, but no error received"); match error { UnitctlError::WaitTimeoutError => {} _ => panic!("Expected WaitTimeoutError: {}", error), } } -#[test] -fn wait_for_unavailable_tcp_socket() { +#[tokio::test] +async fn wait_for_unavailable_tcp_socket() { let control_socket = ControlSocket::try_from("http://127.0.0.1:9783456"); let cli = UnitCtl { control_socket_address: Some(control_socket.unwrap()), @@ -157,7 +159,9 @@ fn wait_for_unavailable_tcp_socket() { }, }; - let error = wait_for_socket(&cli).expect_err("Expected error, but no error received"); + let error = wait_for_socket(&cli) + .await + .expect_err("Expected error, but no error received"); match error { UnitctlError::WaitTimeoutError => {} _ => panic!("Expected WaitTimeoutError"), |