forked from github-starred/komodo
Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3a01f1625 | ||
|
|
aec8fa2bf1 | ||
|
|
7ff2dba82f | ||
|
|
9c86b2d239 | ||
|
|
b1fec7c663 | ||
|
|
8341c6b802 | ||
|
|
73285c4374 | ||
|
|
32d48cdb02 | ||
|
|
8e081fd09c | ||
|
|
04531f1dea | ||
|
|
80f439d472 | ||
|
|
d5e03d6d16 | ||
|
|
a9f55bb8e6 | ||
|
|
9e765f93f5 | ||
|
|
b3aa8e906f | ||
|
|
03fe442aa0 | ||
|
|
d268009a6a | ||
|
|
f0697e812a | ||
|
|
78766463d6 | ||
|
|
0fa1edba2c | ||
|
|
bbd968cac3 | ||
|
|
5f24fc1be3 | ||
|
|
7ecd2b0b0b | ||
|
|
7bf44d2e04 | ||
|
|
24e0672384 | ||
|
|
04f081631f | ||
|
|
b1af956b63 | ||
|
|
370712b29f | ||
|
|
2b6c552964 | ||
|
|
434a1d8ea9 | ||
|
|
0b7f28360f | ||
|
|
3c8ef0ab29 | ||
|
|
930b2423c3 | ||
|
|
546747b5f2 | ||
|
|
c6df866755 | ||
|
|
ea5e684915 | ||
|
|
64db8933de | ||
|
|
7a5580de57 | ||
|
|
b1656bb174 | ||
|
|
559ce103da | ||
|
|
75e278a57b | ||
|
|
430f3ddc34 | ||
|
|
6c30c202e9 | ||
|
|
c5401de1c5 | ||
|
|
7a3d9e0ef6 | ||
|
|
595e3ece42 | ||
|
|
a3bc895755 | ||
|
|
3e3def03ec | ||
|
|
bc672d9649 | ||
|
|
ea6dee4d51 | ||
|
|
b985f18c74 | ||
|
|
45909b2f04 | ||
|
|
2b5a54ce89 | ||
|
|
a18f33b95e | ||
|
|
f35b00ea95 | ||
|
|
70fab08520 | ||
|
|
0331780a5f | ||
|
|
06cdfd2bbc | ||
|
|
1555202569 | ||
|
|
5139622aad | ||
|
|
61ce2ee3db | ||
|
|
3171c14f2b | ||
|
|
521db748d8 | ||
|
|
35bf224080 | ||
|
|
e0b31cfe51 | ||
|
|
0a890078b0 | ||
|
|
df97ced7a4 | ||
|
|
d4e5e2e6d8 | ||
|
|
19aa60dcb5 | ||
|
|
fc19c53e6f | ||
|
|
4f0af960db | ||
|
|
e2ec5258fb | ||
|
|
49b6545a02 | ||
|
|
0aabaa9e62 | ||
|
|
dc65986eab | ||
|
|
1d8f28437d | ||
|
|
c1502e89c2 | ||
|
|
0bd15fc442 | ||
|
|
5a3621b02e | ||
|
|
38192e2dac | ||
|
|
5d271d5547 | ||
|
|
11fb67a35b | ||
|
|
a80499dcc4 |
Generated
+142
-126
@@ -65,6 +65,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -138,9 +144,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0"
|
||||
checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
@@ -187,9 +193,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.8.8"
|
||||
version = "1.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8"
|
||||
checksum = "1856b1b48b65f71a4dd940b1c0931f9a7b646d4a924b9828ffefc1454714668a"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -217,9 +223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-credential-types"
|
||||
version = "1.2.8"
|
||||
version = "1.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc"
|
||||
checksum = "86590e57ea40121d47d3f2e131bfd873dea15d78dc2f4604f4734537ad9e56c4"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -254,9 +260,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.5.12"
|
||||
version = "1.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d"
|
||||
checksum = "8fe0fd441565b0b318c76e7206c8d1d0b0166b3e986cf30e890b61feb6192045"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
@@ -278,9 +284,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ec2"
|
||||
version = "1.175.1"
|
||||
version = "1.188.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1515b09ab70c0991c0b002b0e9114a3ee73b7c5d3d65dfdb047278b208f21b0"
|
||||
checksum = "ce23f4f9971b682019024aed0bf0fd971df7bf86519e41cf2f7ec7a631049395"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -301,9 +307,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.86.0"
|
||||
version = "1.89.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a0abbfab841446cce6e87af853a3ba2cc1bc9afcd3f3550dd556c43d434c86d"
|
||||
checksum = "a9c1b1af02288f729e95b72bd17988c009aa72e26dcb59b3200f86d7aea726c9"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -323,9 +329,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.88.0"
|
||||
version = "1.91.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a68d675582afea0e94d38b6ca9c5aaae4ca14f1d36faa6edb19b42e687e70d7"
|
||||
checksum = "4e8122301558dc7c6c68e878af918880b82ff41897a60c8c4e18e4dc4d93e9f1"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -345,9 +351,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.88.0"
|
||||
version = "1.92.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d30990923f4f675523c51eb1c0dec9b752fb267b36a61e83cbc219c9d86da715"
|
||||
checksum = "a0c7808adcff8333eaa76a849e6de926c6ac1a1268b9fd6afe32de9c29ef29d2"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -368,9 +374,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "1.3.5"
|
||||
version = "1.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68"
|
||||
checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-http",
|
||||
@@ -401,15 +407,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http"
|
||||
version = "0.62.4"
|
||||
version = "0.62.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3feafd437c763db26aa04e0cc7591185d0961e64c61885bece0fb9d50ceac671"
|
||||
checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"http 1.3.1",
|
||||
"http-body 0.4.6",
|
||||
@@ -421,9 +428,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1053b5e587e6fa40ce5a79ea27957b04ba660baa02b28b7436f64850152234f1"
|
||||
checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -440,7 +447,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -451,9 +458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.61.6"
|
||||
version = "0.61.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff418fc8ec5cadf8173b10125f05c2e7e1d46771406187b2c878557d4503390"
|
||||
checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54"
|
||||
dependencies = [
|
||||
"aws-smithy-types",
|
||||
]
|
||||
@@ -479,9 +486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.9.3"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ab99739082da5347660c556689256438defae3bcefd66c52b095905730e404"
|
||||
checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
@@ -503,9 +510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api"
|
||||
version = "1.9.1"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3683c5b152d2ad753607179ed71988e8cfd52964443b4f74fd8e552d0bbfeb46"
|
||||
checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-types",
|
||||
@@ -520,9 +527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f5b3a7486f6690ba25952cabf1e7d75e34d69eaff5081904a47bc79074d6457"
|
||||
checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -546,18 +553,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-xml"
|
||||
version = "0.60.11"
|
||||
version = "0.60.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c34127e8c624bc2999f3b657e749c1393bedc9cd97b92a804db8ced4d2e163"
|
||||
checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-types"
|
||||
version = "1.3.9"
|
||||
version = "1.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1"
|
||||
checksum = "d79fb68e3d7fe5d4833ea34dc87d2e97d26d3086cb3da660bb6b1f76d98680b6"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-async",
|
||||
@@ -569,9 +576,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
|
||||
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
@@ -625,13 +632,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.10.3"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
|
||||
checksum = "dbfe9f610fe4e99cf0cfcd03ccf8c63c28c616fe714d80475ef731f3b13dd21b"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 1.3.1",
|
||||
@@ -639,8 +647,6 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde_core",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -659,9 +665,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-server"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab"
|
||||
checksum = "c1ab4a3ec9ea8a657c72d99a03a824af695bd0fb5ec639ccbd9cd3543b41a5f9"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
@@ -671,7 +677,7 @@ dependencies = [
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -806,9 +812,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bollard"
|
||||
version = "0.19.3"
|
||||
version = "0.19.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7646ee90964aa59e9f832a67182791396a19a5b1d76eb17599a8310a7e2e09"
|
||||
checksum = "87a52479c9237eb04047ddb94788c41ca0d26eaff8b697ecfbb4c32f7fdc3b1b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bollard-stubs",
|
||||
@@ -886,9 +892,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytes-utils"
|
||||
@@ -902,7 +908,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cache"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"tokio",
|
||||
@@ -1019,9 +1025,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.49"
|
||||
version = "4.5.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1029,9 +1035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.49"
|
||||
version = "4.5.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1094,18 +1100,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
"shlex",
|
||||
"svi",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
version = "0.4.31"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23"
|
||||
checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b"
|
||||
dependencies = [
|
||||
"compression-core",
|
||||
"flate2",
|
||||
@@ -1114,13 +1121,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "compression-core"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb"
|
||||
checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"indexmap 2.12.0",
|
||||
@@ -1226,9 +1233,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "croner"
|
||||
version = "3.0.0"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c007081651a19b42931f86f7d4f74ee1c2a7d0cd2c6636a81695b5ffd4e9990"
|
||||
checksum = "4aa42bcd3d846ebf66e15bd528d1087f75d1c6c1c66ebff626178a106353c576"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
@@ -1442,7 +1449,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "database"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -1465,7 +1472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"pem-rfc7468 0.7.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1731,7 +1738,7 @@ dependencies = [
|
||||
"generic-array",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pem-rfc7468 0.7.0",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
@@ -1741,7 +1748,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1783,7 +1790,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"formatting",
|
||||
@@ -1793,7 +1800,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -1878,6 +1885,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -1889,7 +1902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -2055,14 +2068,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cache",
|
||||
"command",
|
||||
"formatting",
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -2127,21 +2139,23 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
@@ -2454,7 +2468,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -2688,7 +2702,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interpolate"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2793,9 +2807,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "10.0.0"
|
||||
version = "10.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1417155a38e99d7704ddb3ea7445fe57fdbd5d756d727740a9ed8b9ebaed6e1"
|
||||
checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"base64 0.22.1",
|
||||
@@ -2810,10 +2824,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
@@ -2832,18 +2845,19 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"wildcard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
"bson",
|
||||
"bytes",
|
||||
"clap",
|
||||
"derive_builder",
|
||||
"derive_default_builder",
|
||||
@@ -2874,7 +2888,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2921,10 +2935,11 @@ dependencies = [
|
||||
"reqwest",
|
||||
"resolver_api",
|
||||
"response",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"serde_yaml_ng",
|
||||
"serror",
|
||||
"sha2",
|
||||
@@ -2947,7 +2962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2978,14 +2993,14 @@ dependencies = [
|
||||
"periphery_client",
|
||||
"portable-pty",
|
||||
"resolver_api",
|
||||
"run_command",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml_ng",
|
||||
"serror",
|
||||
"shell-escape",
|
||||
"shlex",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -3068,7 +3083,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -3299,7 +3314,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rustc_version_runtime",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
@@ -3360,7 +3375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "noise"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3368,7 +3383,7 @@ dependencies = [
|
||||
"colored",
|
||||
"der",
|
||||
"komodo_client",
|
||||
"pem-rfc7468",
|
||||
"pem-rfc7468 1.0.0",
|
||||
"pkcs8",
|
||||
"secret_file",
|
||||
"serde_json",
|
||||
@@ -3658,11 +3673,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ordered_hash_map"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176"
|
||||
checksum = "a6c699f8a30f345785be969deed7eee4c73a5de58c7faf61d6a3251ef798ff61"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"hashbrown 0.15.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -3767,6 +3782,15 @@ dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -3775,7 +3799,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_variants",
|
||||
@@ -3995,7 +4019,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"socket2 0.6.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
@@ -4015,7 +4039,7 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.17",
|
||||
@@ -4207,7 +4231,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
@@ -4256,7 +4280,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "response"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4309,15 +4333,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "run_command"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388e1106aa4bd809ba57afecb8b3efc60b6599cd06a774d5798a7c5a29675307"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -4370,9 +4385,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.33"
|
||||
version = "0.23.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
|
||||
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
@@ -4535,7 +4550,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "secret_file"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
@@ -5281,7 +5296,7 @@ version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -5305,7 +5320,7 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -5315,9 +5330,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.16"
|
||||
version = "0.7.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
|
||||
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -5362,9 +5377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_pretty"
|
||||
version = "1.2.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd195be7600b4b26e951a25b382b67a976d2d44bb967a1a5b573a3e63d18c29"
|
||||
checksum = "abc44e8fe3b5ec4cce2db87a3c11e0ebc396d4c2d59da64d6f0c00e78f9b9296"
|
||||
dependencies = [
|
||||
"ordered_hash_map",
|
||||
"serde",
|
||||
@@ -5568,19 +5583,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "transport"
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"colored",
|
||||
"encoding",
|
||||
"futures-util",
|
||||
"noise",
|
||||
"periphery_client",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"serde",
|
||||
"serror",
|
||||
"sha1",
|
||||
@@ -5611,7 +5627,7 @@ dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rustls 0.23.33",
|
||||
"rustls 0.23.35",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.17",
|
||||
|
||||
+19
-19
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.0.0-dev-72"
|
||||
version = "2.0.0-dev-91"
|
||||
edition = "2024"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -39,7 +39,6 @@ noise = { path = "lib/noise" }
|
||||
git = { path = "lib/git" }
|
||||
|
||||
# MOGH
|
||||
run_command = { version = "0.0.6", features = ["async_tokio"] }
|
||||
serror = { version = "0.5.3", default-features = false }
|
||||
slack = { version = "2.0.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
derive_default_builder = "0.1.8"
|
||||
@@ -49,14 +48,14 @@ partial_derive2 = "0.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "2.0.2"
|
||||
resolver_api = "3.0.0"
|
||||
toml_pretty = "1.2.0"
|
||||
toml_pretty = "2.0.0"
|
||||
mungos = "3.2.2"
|
||||
svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.12.24", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.16", features = ["io", "codec"] }
|
||||
tokio-util = { version = "0.7.17", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["sync"] }
|
||||
pin-project-lite = "0.2.16"
|
||||
futures-util = "0.3.31"
|
||||
@@ -64,10 +63,10 @@ arc-swap = "1.7.1"
|
||||
|
||||
# SERVER
|
||||
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
|
||||
axum-extra = { version = "0.10.3", features = ["typed-header"] }
|
||||
axum-extra = { version = "0.12.2", features = ["typed-header"] }
|
||||
tower-http = { version = "0.6.6", features = ["fs", "cors"] }
|
||||
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
|
||||
axum = { version = "0.8.6", features = ["ws", "json", "macros"] }
|
||||
axum-server = { version = "0.7.3", features = ["tls-rustls"] }
|
||||
axum = { version = "0.8.7", features = ["ws", "json", "macros"] }
|
||||
|
||||
# SER/DE
|
||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||
@@ -95,16 +94,16 @@ opentelemetry = "0.31.0"
|
||||
tracing = "0.1.41"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.49", features = ["derive"] }
|
||||
clap = { version = "4.5.52", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
openidconnect = { version = "4.0.1", features = ["accept-rfc3339-timestamps"] }
|
||||
uuid = { version = "1.18.1", features = ["v4", "fast-rng", "serde"] }
|
||||
jsonwebtoken = { version = "10.0.0", features = ["aws_lc_rs"] } # locked back with octorust
|
||||
rustls = { version = "0.23.33", features = ["aws-lc-rs"] }
|
||||
pem-rfc7468 = { version = "0.7.0", features = ["alloc"] }
|
||||
openidconnect = "4.0.1"
|
||||
jsonwebtoken = { version = "10.2.0", features = ["aws_lc_rs"] } # locked back with octorust
|
||||
rustls = { version = "0.23.35", features = ["aws-lc-rs"] }
|
||||
pem-rfc7468 = { version = "1.0.0", features = ["alloc"] }
|
||||
urlencoding = "2.1.3"
|
||||
bcrypt = "0.17.1"
|
||||
base64 = "0.22.1"
|
||||
@@ -123,27 +122,28 @@ hickory-resolver = "0.25.2"
|
||||
portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.19.3"
|
||||
bollard = "0.19.4"
|
||||
sysinfo = "0.37.1"
|
||||
shlex = "1.3.0"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.8.8"
|
||||
aws-sdk-ec2 = "1.175.1"
|
||||
aws-credential-types = "1.2.8"
|
||||
aws-config = "1.8.10"
|
||||
aws-sdk-ec2 = "1.188.0"
|
||||
aws-credential-types = "1.2.9"
|
||||
|
||||
## CRON
|
||||
english-to-cron = "0.1.6"
|
||||
chrono-tz = "0.10.4"
|
||||
chrono = "0.4.42"
|
||||
croner = "3.0.0"
|
||||
croner = "3.0.1"
|
||||
|
||||
# MISC
|
||||
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
|
||||
async-compression = { version = "0.4.33", features = ["tokio", "gzip"] }
|
||||
derive_builder = "0.20.2"
|
||||
comfy-table = "7.2.1"
|
||||
typeshare = "1.0.4"
|
||||
dashmap = "6.1.0"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.0.0"
|
||||
bytes = "1.11.0"
|
||||
regex = "1.12.2"
|
||||
bytes = "1.10.1"
|
||||
+1
-2
@@ -21,9 +21,9 @@ config.workspace = true
|
||||
logger.workspace = true
|
||||
noise.workspace = true
|
||||
# external
|
||||
tokio-tungstenite.workspace = true
|
||||
futures-util.workspace = true
|
||||
comfy-table.workspace = true
|
||||
tokio-util.workspace = true
|
||||
serde_json.workspace = true
|
||||
crossterm.workspace = true
|
||||
serde_qs.workspace = true
|
||||
@@ -33,7 +33,6 @@ colored.workspace = true
|
||||
dotenvy.workspace = true
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
bytes.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
clap.workspace = true
|
||||
|
||||
@@ -61,7 +61,8 @@ async fn list_containers(
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllDockerContainers {
|
||||
servers: Default::default()
|
||||
servers: Default::default(),
|
||||
containers: Default::default(),
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -145,7 +146,8 @@ pub async fn inspect_container(
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllDockerContainers {
|
||||
servers: Default::default()
|
||||
servers: Default::default(),
|
||||
containers: Default::default()
|
||||
}),
|
||||
)?;
|
||||
|
||||
|
||||
+19
-38
@@ -5,10 +5,9 @@ use futures_util::{FutureExt, try_join};
|
||||
use komodo_client::{
|
||||
KomodoClient,
|
||||
api::read::{
|
||||
ListActions, ListAlerters, ListAllTerminals, ListBuilders,
|
||||
ListBuilds, ListDeployments, ListProcedures, ListRepos,
|
||||
ListResourceSyncs, ListSchedules, ListServers, ListStacks,
|
||||
ListTags,
|
||||
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
||||
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
||||
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
|
||||
},
|
||||
entities::{
|
||||
ResourceTargetVariant,
|
||||
@@ -27,20 +26,16 @@ use komodo_client::{
|
||||
ProcedureListItem, ProcedureListItemInfo, ProcedureState,
|
||||
},
|
||||
repo::{RepoListItem, RepoListItemInfo, RepoState},
|
||||
resource::{
|
||||
ResourceListItem, ResourceQuery, TemplatesQueryBehavior,
|
||||
},
|
||||
resource::{ResourceListItem, ResourceQuery},
|
||||
resource_link,
|
||||
schedule::Schedule,
|
||||
server::{
|
||||
ServerListItem, ServerListItemInfo, ServerState,
|
||||
TerminalInfoWithServer,
|
||||
},
|
||||
server::{ServerListItem, ServerListItemInfo, ServerState},
|
||||
stack::{StackListItem, StackListItemInfo, StackState},
|
||||
sync::{
|
||||
ResourceSyncListItem, ResourceSyncListItemInfo,
|
||||
ResourceSyncState,
|
||||
},
|
||||
terminal::Terminal,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
@@ -202,30 +197,16 @@ async fn list_terminals(
|
||||
filters: &ResourceFilters,
|
||||
) -> anyhow::Result<()> {
|
||||
let client = crate::command::komodo_client().await?;
|
||||
let query = ResourceQuery::builder()
|
||||
.tags(filters.tags.clone())
|
||||
.templates(TemplatesQueryBehavior::Exclude)
|
||||
.build();
|
||||
let (mut terminals, servers) = tokio::try_join!(
|
||||
client.read(ListAllTerminals {
|
||||
query: query.clone(),
|
||||
fresh: true,
|
||||
}),
|
||||
client
|
||||
.read(ListServers { query })
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>()))
|
||||
)?;
|
||||
// Fix server ids -> names
|
||||
terminals.iter_mut().for_each(|terminal| {
|
||||
let Some(name) = servers.get(&terminal.server_id) else {
|
||||
terminal.server_id = String::new();
|
||||
return;
|
||||
};
|
||||
terminal.server_id.clone_from(name);
|
||||
});
|
||||
// let query = ResourceQuery::builder()
|
||||
// .tags(filters.tags.clone())
|
||||
// .templates(TemplatesQueryBehavior::Exclude)
|
||||
// .build();
|
||||
let terminals = client
|
||||
.read(ListTerminals {
|
||||
target: None,
|
||||
use_names: true,
|
||||
})
|
||||
.await?;
|
||||
if !terminals.is_empty() {
|
||||
print_items(terminals, filters.format, filters.links)?;
|
||||
}
|
||||
@@ -1177,14 +1158,14 @@ impl PrintTable for ResourceListItem<AlerterListItemInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for TerminalInfoWithServer {
|
||||
impl PrintTable for Terminal {
|
||||
fn header(_links: bool) -> &'static [&'static str] {
|
||||
&["Terminal", "Server", "Command", "Size", "Created"]
|
||||
&["Terminal", "Target", "Command", "Size", "Created"]
|
||||
}
|
||||
fn row(self, _links: bool) -> Vec<comfy_table::Cell> {
|
||||
vec![
|
||||
Cell::new(self.name).add_attribute(Attribute::Bold),
|
||||
Cell::new(self.server_id),
|
||||
Cell::new(format!("{:?}", self.target)),
|
||||
Cell::new(self.command),
|
||||
Cell::new(if self.stored_size_kb < 1.0 {
|
||||
format!("{:.1} KiB", self.stored_size_kb)
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod container;
|
||||
pub mod database;
|
||||
pub mod execute;
|
||||
pub mod list;
|
||||
pub mod ssh;
|
||||
pub mod terminal;
|
||||
pub mod update;
|
||||
|
||||
async fn komodo_client() -> anyhow::Result<&'static KomodoClient> {
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use colored::Colorize;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use komodo_client::{
|
||||
api::write::{CreateTerminal, TerminalRecreateMode},
|
||||
entities::config::cli::args::ssh::Ssh,
|
||||
};
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
use tokio_tungstenite::tungstenite;
|
||||
|
||||
pub async fn handle(
|
||||
Ssh {
|
||||
server,
|
||||
name,
|
||||
command,
|
||||
recreate,
|
||||
}: &Ssh,
|
||||
) -> anyhow::Result<()> {
|
||||
// Need to forward multiple sources into ws write
|
||||
let (write_tx, mut write_rx) =
|
||||
tokio::sync::mpsc::channel::<Bytes>(1024);
|
||||
|
||||
// ================
|
||||
// SETUP RESIZING
|
||||
// ================
|
||||
|
||||
// Subscribe to SIGWINCH for resize messages
|
||||
let mut sigwinch = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::window_change(),
|
||||
)
|
||||
.context("failed to register SIGWINCH handler")?;
|
||||
|
||||
// Send first resize messsage, bailing if it fails to get the size.
|
||||
write_tx.send(resize_message()?).await?;
|
||||
|
||||
let forward_resize = async {
|
||||
while sigwinch.recv().await.is_some() {
|
||||
if let Ok(resize_message) = resize_message()
|
||||
&& write_tx.send(resize_message).await.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let forward_stdin = async {
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut buf = [0u8; 8192];
|
||||
loop {
|
||||
// Read into buffer starting from index 1,
|
||||
// leaving first byte to represent 'data' message.
|
||||
let n = match stdin.read(&mut buf[1..]).await {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(n) => n,
|
||||
Err(_) => break,
|
||||
};
|
||||
// Check for disconnect sequence (alt + q)
|
||||
if buf[1..(n + 1)] == [197, 147] {
|
||||
break;
|
||||
}
|
||||
let bytes = Bytes::copy_from_slice(&buf[..(n + 1)]);
|
||||
if write_tx.send(bytes).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// =====================
|
||||
// CONNECT AND FORWARD
|
||||
// =====================
|
||||
|
||||
let client = super::komodo_client().await?;
|
||||
|
||||
// Init the terminal if it doesn't exist already.
|
||||
client
|
||||
.write(CreateTerminal {
|
||||
server: server.to_string(),
|
||||
name: name.to_string(),
|
||||
command: command.clone(),
|
||||
recreate: if *recreate {
|
||||
TerminalRecreateMode::Always
|
||||
} else {
|
||||
TerminalRecreateMode::DifferentCommand
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (mut ws_write, mut ws_read) = client
|
||||
.connect_terminal_websocket(server, name)
|
||||
.await?
|
||||
.split();
|
||||
|
||||
let forward_write = async {
|
||||
while let Some(bytes) = write_rx.recv().await {
|
||||
if let Err(e) =
|
||||
ws_write.send(tungstenite::Message::Binary(bytes)).await
|
||||
{
|
||||
return Some(e);
|
||||
};
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let forward_read = async {
|
||||
let mut stdout = tokio::io::stdout();
|
||||
loop {
|
||||
match ws_read.next().await {
|
||||
Some(Ok(tungstenite::Message::Binary(bytes))) => {
|
||||
if let Err(e) =
|
||||
tokio::io::copy(&mut bytes.as_ref(), &mut stdout)
|
||||
.await
|
||||
.context("Failed to copy bytes to stdout")
|
||||
{
|
||||
return Some(e);
|
||||
}
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
Some(Ok(tungstenite::Message::Text(text))) => {
|
||||
if let Err(e) =
|
||||
tokio::io::copy(&mut text.as_ref(), &mut stdout)
|
||||
.await
|
||||
.context("Failed to copy text to stdout")
|
||||
{
|
||||
return Some(e);
|
||||
}
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
Some(Ok(tungstenite::Message::Close(_))) => break,
|
||||
Some(Err(e)) => {
|
||||
return Some(
|
||||
anyhow::Error::from(e).context("Websocket read error"),
|
||||
);
|
||||
}
|
||||
None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let guard = RawModeGuard::enable_raw_mode()?;
|
||||
|
||||
tokio::select! {
|
||||
_ = forward_resize => drop(guard),
|
||||
_ = forward_stdin => drop(guard),
|
||||
e = forward_write => {
|
||||
drop(guard);
|
||||
if let Some(e) = e {
|
||||
eprintln!("\nFailed to forward stdin | {e:#}");
|
||||
}
|
||||
},
|
||||
e = forward_read => {
|
||||
drop(guard);
|
||||
if let Some(e) = e {
|
||||
eprintln!("\nFailed to forward stdout | {e:#}");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
println!("\n\n{} {}", "connection".bold(), "closed".red().bold());
|
||||
|
||||
// It doesn't seem to exit by itself after the raw mode stuff.
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
||||
fn resize_message() -> anyhow::Result<Bytes> {
|
||||
let (cols, rows) = crossterm::terminal::size()
|
||||
.context("Failed to get terminal size")?;
|
||||
let bytes: Vec<u8> =
|
||||
format!(r#"{{"rows":{rows},"cols":{cols}}}"#).into();
|
||||
let mut msg = Vec::with_capacity(bytes.len() + 1);
|
||||
msg.push(0xff); // resize prefix
|
||||
msg.extend(bytes);
|
||||
Ok(msg.into())
|
||||
}
|
||||
|
||||
struct RawModeGuard;
|
||||
|
||||
impl RawModeGuard {
|
||||
fn enable_raw_mode() -> anyhow::Result<Self> {
|
||||
crossterm::terminal::enable_raw_mode()
|
||||
.context("Failed to enable terminal raw mode")?;
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
impl Drop for RawModeGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = crossterm::terminal::disable_raw_mode() {
|
||||
eprintln!("Failed to disable terminal raw mode | {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use colored::Colorize;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
read::{ListAllDockerContainers, ListServers},
|
||||
terminal::InitTerminal,
|
||||
},
|
||||
entities::{
|
||||
config::cli::args::terminal::{Attach, Connect, Exec},
|
||||
server::ServerQuery,
|
||||
terminal::{
|
||||
ContainerTerminalMode, TerminalRecreateMode,
|
||||
TerminalResizeMessage, TerminalStdinMessage,
|
||||
},
|
||||
},
|
||||
ws::terminal::TerminalWebsocket,
|
||||
};
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub async fn handle_connect(
|
||||
Connect {
|
||||
server,
|
||||
name,
|
||||
command,
|
||||
recreate,
|
||||
}: &Connect,
|
||||
) -> anyhow::Result<()> {
|
||||
handle_terminal_forwarding(async {
|
||||
super::komodo_client()
|
||||
.await?
|
||||
.connect_server_terminal(
|
||||
server.to_string(),
|
||||
Some(name.to_string()),
|
||||
Some(InitTerminal {
|
||||
command: command.clone(),
|
||||
recreate: if *recreate {
|
||||
TerminalRecreateMode::Always
|
||||
} else {
|
||||
TerminalRecreateMode::DifferentCommand
|
||||
},
|
||||
mode: None,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn handle_exec(
|
||||
Exec {
|
||||
server,
|
||||
container,
|
||||
shell,
|
||||
recreate,
|
||||
}: &Exec,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = get_server(server.clone(), container).await?;
|
||||
handle_terminal_forwarding(async {
|
||||
super::komodo_client()
|
||||
.await?
|
||||
.connect_container_terminal(
|
||||
server,
|
||||
container.to_string(),
|
||||
None,
|
||||
Some(InitTerminal {
|
||||
command: Some(shell.to_string()),
|
||||
recreate: if *recreate {
|
||||
TerminalRecreateMode::Always
|
||||
} else {
|
||||
TerminalRecreateMode::DifferentCommand
|
||||
},
|
||||
mode: Some(ContainerTerminalMode::Exec),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn handle_attach(
|
||||
Attach {
|
||||
server,
|
||||
container,
|
||||
recreate,
|
||||
}: &Attach,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = get_server(server.clone(), container).await?;
|
||||
handle_terminal_forwarding(async {
|
||||
super::komodo_client()
|
||||
.await?
|
||||
.connect_container_terminal(
|
||||
server,
|
||||
container.to_string(),
|
||||
None,
|
||||
Some(InitTerminal {
|
||||
command: None,
|
||||
recreate: if *recreate {
|
||||
TerminalRecreateMode::Always
|
||||
} else {
|
||||
TerminalRecreateMode::DifferentCommand
|
||||
},
|
||||
mode: Some(ContainerTerminalMode::Attach),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_server(
|
||||
server: Option<String>,
|
||||
container: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
if let Some(server) = server {
|
||||
return Ok(server);
|
||||
}
|
||||
|
||||
let client = super::komodo_client().await?;
|
||||
|
||||
let mut containers = client
|
||||
.read(ListAllDockerContainers {
|
||||
servers: Default::default(),
|
||||
containers: vec![container.to_string()],
|
||||
})
|
||||
.await?;
|
||||
|
||||
if containers.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Did not find any container matching {container}"
|
||||
));
|
||||
}
|
||||
|
||||
if containers.len() == 1 {
|
||||
return containers
|
||||
.pop()
|
||||
.context("Shouldn't happen")?
|
||||
.server_id
|
||||
.context("Container doesn't have server_id");
|
||||
}
|
||||
|
||||
let servers = containers
|
||||
.into_iter()
|
||||
.flat_map(|container| container.server_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let servers = client
|
||||
.read(ListServers {
|
||||
query: ServerQuery::builder().names(servers).build(),
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|server| format!("\t- {}", server.name.bold()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
Err(anyhow!(
|
||||
"Multiple containers matching '{}' on Servers:\n{servers}",
|
||||
container.bold(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle_terminal_forwarding<
|
||||
C: Future<Output = anyhow::Result<TerminalWebsocket>>,
|
||||
>(
|
||||
connect: C,
|
||||
) -> anyhow::Result<()> {
|
||||
// Need to forward multiple sources into ws write
|
||||
let (write_tx, mut write_rx) =
|
||||
tokio::sync::mpsc::channel::<TerminalStdinMessage>(1024);
|
||||
|
||||
// ================
|
||||
// SETUP RESIZING
|
||||
// ================
|
||||
|
||||
// Subscribe to SIGWINCH for resize messages
|
||||
let mut sigwinch = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::window_change(),
|
||||
)
|
||||
.context("failed to register SIGWINCH handler")?;
|
||||
|
||||
// Send first resize messsage, bailing if it fails to get the size.
|
||||
write_tx.send(resize_message()?).await?;
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
let forward_resize = async {
|
||||
while future_or_cancel(sigwinch.recv(), &cancel)
|
||||
.await
|
||||
.flatten()
|
||||
.is_some()
|
||||
{
|
||||
if let Ok(resize_message) = resize_message()
|
||||
&& write_tx.send(resize_message).await.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
cancel.cancel();
|
||||
};
|
||||
|
||||
let forward_stdin = async {
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut buf = [0u8; 8192];
|
||||
while let Some(Ok(n)) =
|
||||
future_or_cancel(stdin.read(&mut buf), &cancel).await
|
||||
{
|
||||
// EOF
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
let bytes = &buf[..n];
|
||||
// Check for disconnect sequence (alt + q)
|
||||
if bytes == [197, 147] {
|
||||
break;
|
||||
}
|
||||
// Forward bytes
|
||||
if write_tx
|
||||
.send(TerminalStdinMessage::Forward(bytes.to_vec()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
};
|
||||
}
|
||||
cancel.cancel();
|
||||
};
|
||||
|
||||
// =====================
|
||||
// CONNECT AND FORWARD
|
||||
// =====================
|
||||
|
||||
let (mut ws_write, mut ws_read) = connect.await?.split();
|
||||
|
||||
let forward_write = async {
|
||||
while let Some(message) =
|
||||
future_or_cancel(write_rx.recv(), &cancel).await.flatten()
|
||||
{
|
||||
if let Err(e) = ws_write.send_stdin_message(message).await {
|
||||
cancel.cancel();
|
||||
return Some(e);
|
||||
};
|
||||
}
|
||||
cancel.cancel();
|
||||
None
|
||||
};
|
||||
|
||||
let forward_read = async {
|
||||
let mut stdout = tokio::io::stdout();
|
||||
while let Some(msg) =
|
||||
future_or_cancel(ws_read.receive_stdout(), &cancel).await
|
||||
{
|
||||
let bytes = match msg {
|
||||
Ok(Some(bytes)) => bytes,
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
cancel.cancel();
|
||||
return Some(e.context("Websocket read error"));
|
||||
}
|
||||
};
|
||||
if let Err(e) = stdout
|
||||
.write_all(&bytes)
|
||||
.await
|
||||
.context("Failed to write text to stdout")
|
||||
{
|
||||
cancel.cancel();
|
||||
return Some(e);
|
||||
}
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
cancel.cancel();
|
||||
None
|
||||
};
|
||||
|
||||
let guard = RawModeGuard::enable_raw_mode()?;
|
||||
|
||||
let (_, _, write_error, read_error) = tokio::join!(
|
||||
forward_resize,
|
||||
forward_stdin,
|
||||
forward_write,
|
||||
forward_read
|
||||
);
|
||||
|
||||
drop(guard);
|
||||
|
||||
if let Some(e) = write_error {
|
||||
eprintln!("\nFailed to forward stdin | {e:#}");
|
||||
}
|
||||
|
||||
if let Some(e) = read_error {
|
||||
eprintln!("\nFailed to forward stdout | {e:#}");
|
||||
}
|
||||
|
||||
println!("\n\n{} {}", "connection".bold(), "closed".red().bold());
|
||||
|
||||
// It doesn't seem to exit by itself after the raw mode stuff.
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
||||
fn resize_message() -> anyhow::Result<TerminalStdinMessage> {
|
||||
let (cols, rows) = crossterm::terminal::size()
|
||||
.context("Failed to get terminal size")?;
|
||||
Ok(TerminalStdinMessage::Resize(TerminalResizeMessage {
|
||||
rows,
|
||||
cols,
|
||||
}))
|
||||
}
|
||||
|
||||
struct RawModeGuard;
|
||||
|
||||
impl RawModeGuard {
|
||||
fn enable_raw_mode() -> anyhow::Result<Self> {
|
||||
crossterm::terminal::enable_raw_mode()
|
||||
.context("Failed to enable terminal raw mode")?;
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
impl Drop for RawModeGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = crossterm::terminal::disable_raw_mode() {
|
||||
eprintln!("Failed to disable terminal raw mode | {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn future_or_cancel<T, F: Future<Output = T>>(
|
||||
fut: F,
|
||||
cancel: &CancellationToken,
|
||||
) -> Option<T> {
|
||||
tokio::select! {
|
||||
res = fut => Some(res),
|
||||
_ = cancel.cancelled() => None
|
||||
}
|
||||
}
|
||||
+23
-3
@@ -2,6 +2,7 @@
|
||||
extern crate tracing;
|
||||
|
||||
use anyhow::Context;
|
||||
use colored::Colorize;
|
||||
use komodo_client::entities::config::cli::args;
|
||||
|
||||
use crate::config::cli_config;
|
||||
@@ -54,7 +55,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
args::Command::Update { command } => {
|
||||
command::update::handle(command).await
|
||||
}
|
||||
args::Command::Ssh(ssh) => command::ssh::handle(ssh).await,
|
||||
args::Command::Connect(connect) => {
|
||||
command::terminal::handle_connect(connect).await
|
||||
}
|
||||
args::Command::Exec(exec) => {
|
||||
command::terminal::handle_exec(exec).await
|
||||
}
|
||||
args::Command::Attach(attach) => {
|
||||
command::terminal::handle_attach(attach).await
|
||||
}
|
||||
args::Command::Key { command } => {
|
||||
noise::key::command::handle(command).await
|
||||
}
|
||||
@@ -70,7 +79,18 @@ async fn main() -> anyhow::Result<()> {
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
tokio::select! {
|
||||
res = tokio::spawn(app()) => res?,
|
||||
_ = term_signal.recv() => Ok(()),
|
||||
res = tokio::spawn(app()) => match res {
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("{}: {e}", "ERROR".red());
|
||||
std::process::exit(1)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}: {e}", "ERROR".red());
|
||||
std::process::exit(1)
|
||||
},
|
||||
Ok(_) => {}
|
||||
},
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ axum-extra.workspace = true
|
||||
tower-http.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml_ng.workspace = true
|
||||
serde_qs.workspace = true
|
||||
typeshare.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
indexmap.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.90.0-bullseye AS core-builder
|
||||
FROM rust:1.90.0-trixie AS core-builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
@@ -26,7 +26,7 @@ RUN cd client && yarn && yarn build && yarn link
|
||||
RUN cd frontend && yarn link komodo_client && yarn && yarn build
|
||||
|
||||
# Final Image
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/core/starship.toml /starship.toml
|
||||
COPY ./bin/core/debian-deps.sh .
|
||||
|
||||
@@ -13,7 +13,7 @@ FROM ${AARCH64_BINARIES} AS aarch64
|
||||
FROM ${FRONTEND_IMAGE} AS frontend
|
||||
|
||||
# Final Image
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/core/starship.toml /starship.toml
|
||||
COPY ./bin/core/debian-deps.sh .
|
||||
|
||||
@@ -14,7 +14,7 @@ COPY ./client/core/ts ./client
|
||||
RUN cd client && yarn && yarn build && yarn link
|
||||
RUN cd frontend && yarn link komodo_client && yarn && yarn build
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/core/starship.toml /starship.toml
|
||||
COPY ./bin/core/debian-deps.sh .
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use config::merge_objects;
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::bson::to_document,
|
||||
@@ -178,7 +178,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
""
|
||||
};
|
||||
|
||||
let mut res = run_komodo_command(
|
||||
let mut res = run_komodo_standard_command(
|
||||
// Keep this stage name as is, the UI will find the latest update log by matching the stage name
|
||||
"Execute Action",
|
||||
None,
|
||||
|
||||
@@ -104,22 +104,23 @@ impl Resolve<ExecuteArgs> for SendAlert {
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let alerters =
|
||||
list_full_for_user::<Alerter>(Default::default(), user, &[])
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|a| {
|
||||
a.config.enabled
|
||||
&& (self.alerters.is_empty()
|
||||
|| self.alerters.contains(&a.name)
|
||||
|| self.alerters.contains(&a.id))
|
||||
&& (a.config.alert_types.is_empty()
|
||||
|| a
|
||||
.config
|
||||
.alert_types
|
||||
.contains(&AlertDataVariant::Custom))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let alerters = list_full_for_user::<Alerter>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|a| {
|
||||
a.config.enabled
|
||||
&& (self.alerters.is_empty()
|
||||
|| self.alerters.contains(&a.name)
|
||||
|| self.alerters.contains(&a.id))
|
||||
&& (a.config.alert_types.is_empty()
|
||||
|| a.config.alert_types.contains(&AlertDataVariant::Custom))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let alerters = if user.admin {
|
||||
alerters
|
||||
|
||||
@@ -17,9 +17,12 @@ use formatting::format_serror;
|
||||
use futures_util::future::join_all;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
|
||||
RunBuild,
|
||||
api::{
|
||||
execute::{
|
||||
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
|
||||
RunBuild,
|
||||
},
|
||||
write::RefreshBuildCache,
|
||||
},
|
||||
entities::{
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
@@ -41,6 +44,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
alert::send_alerts,
|
||||
api::write::WriteArgs,
|
||||
helpers::{
|
||||
build_git_token,
|
||||
builder::{cleanup_builder_instance, connect_builder_periphery},
|
||||
@@ -186,7 +190,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
update.finalize();
|
||||
let id = update.id.clone();
|
||||
if let Err(e) = update_update(update).await {
|
||||
warn!("failed to modify Update {id} on db | {e:#}");
|
||||
warn!("Failed to modify Update {id} on db | {e:#}");
|
||||
}
|
||||
if !is_server_builder {
|
||||
cancel_clone.cancel();
|
||||
@@ -215,12 +219,12 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
Ok(builder) => builder,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"failed to get builder for build {} | {e:#}",
|
||||
"Failed to get Builder for Build {} | {e:#}",
|
||||
build.name
|
||||
);
|
||||
update.logs.push(Log::error(
|
||||
"get builder",
|
||||
format_serror(&e.context("failed to get builder").into()),
|
||||
"Get Builder",
|
||||
format_serror(&e.context("Failed to get Builder").into()),
|
||||
));
|
||||
return handle_early_return(
|
||||
update, build.id, build.name, false,
|
||||
@@ -276,7 +280,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
|
||||
let commit_message = match res {
|
||||
Ok(res) => {
|
||||
debug!("finished repo clone");
|
||||
debug!("Finished repo clone");
|
||||
update.logs.extend(res.res.logs);
|
||||
update.commit_hash =
|
||||
res.res.commit_hash.unwrap_or_default().to_string();
|
||||
@@ -312,10 +316,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
commit_hash: optional_string(&update.commit_hash),
|
||||
// Unused for now
|
||||
additional_tags: Default::default(),
|
||||
}) => res.context("failed at call to periphery to build"),
|
||||
}) => res.context("Failed at call to Periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
info!("Build cancelled during build, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("user cancelled build during docker build"));
|
||||
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
@@ -328,10 +332,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
update.logs.extend(logs);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error in build | {e:#}");
|
||||
warn!("Error in build | {e:#}");
|
||||
update.push_error_log(
|
||||
"Build Error",
|
||||
format_serror(&e.context("failed to build").into()),
|
||||
format_serror(&e.context("Failed to build").into()),
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -382,12 +386,15 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let Build { id, name, .. } = build;
|
||||
|
||||
if update.success {
|
||||
// don't hold response up for user
|
||||
tokio::spawn(async move {
|
||||
handle_post_build_redeploy(&build.id).await;
|
||||
handle_post_build_redeploy(&id).await;
|
||||
});
|
||||
} else {
|
||||
let name = name.clone();
|
||||
let target = update.target.clone();
|
||||
let version = update.version;
|
||||
tokio::spawn(async move {
|
||||
@@ -398,16 +405,22 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
resolved_ts: Some(komodo_timestamp()),
|
||||
resolved: true,
|
||||
level: SeverityLevel::Warning,
|
||||
data: AlertData::BuildFailed {
|
||||
id: build.id,
|
||||
name: build.name,
|
||||
version,
|
||||
},
|
||||
data: AlertData::BuildFailed { id, name, version },
|
||||
};
|
||||
send_alerts(&[alert]).await
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(e) = (RefreshBuildCache { build: name })
|
||||
.resolve(&WriteArgs { user: user.clone() })
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh build cache",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(update.clone())
|
||||
}
|
||||
}
|
||||
@@ -565,7 +578,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"failed to set CancelBuild Update status Complete after timeout | {e:#}"
|
||||
"Failed to set CancelBuild Update status Complete after timeout | {e:#}"
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{fmt::Write as _, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use database::{
|
||||
bson::{Document, doc},
|
||||
mungos::find::find_collect,
|
||||
@@ -151,7 +151,7 @@ impl Resolve<ExecuteArgs> for BackupCoreDatabase {
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let res = run_komodo_command(
|
||||
let res = run_komodo_standard_command(
|
||||
"Backup Core Database",
|
||||
None,
|
||||
"km database backup --yes",
|
||||
|
||||
@@ -8,9 +8,7 @@ use axum_extra::{TypedHeader, headers::ContentType};
|
||||
use database::mungos::by_id::find_one_by_id;
|
||||
use derive_variants::{EnumVariants, ExtractVariant};
|
||||
use formatting::format_serror;
|
||||
use futures_util::{
|
||||
StreamExt as _, future::join_all, stream::FuturesUnordered,
|
||||
};
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
@@ -32,7 +30,6 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
auth::auth_request,
|
||||
helpers::update::{init_execution_update, update_update},
|
||||
permission::get_check_permissions,
|
||||
resource::{KomodoResource, list_full_for_user_using_pattern},
|
||||
state::db_client,
|
||||
};
|
||||
@@ -145,13 +142,13 @@ pub enum ExecuteRequest {
|
||||
RunAction(RunAction),
|
||||
BatchRunAction(BatchRunAction),
|
||||
|
||||
// ==== SYNC ====
|
||||
RunSync(RunSync),
|
||||
|
||||
// ==== ALERTER ====
|
||||
TestAlerter(TestAlerter),
|
||||
SendAlert(SendAlert),
|
||||
|
||||
// ==== SYNC ====
|
||||
RunSync(RunSync),
|
||||
|
||||
// ==== MAINTENANCE ====
|
||||
ClearRepoCache(ClearRepoCache),
|
||||
BackupCoreDatabase(BackupCoreDatabase),
|
||||
@@ -324,32 +321,11 @@ async fn batch_execute<E: BatchExecute>(
|
||||
pattern,
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let resources = if user.admin {
|
||||
resources
|
||||
} else {
|
||||
// Only keep resources with execute permissions
|
||||
resources
|
||||
.into_iter()
|
||||
.map(|resource| async move {
|
||||
get_check_permissions::<E::Resource>(
|
||||
&resource.id,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
)
|
||||
.await
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect()
|
||||
};
|
||||
|
||||
let futures = resources.into_iter().map(|resource| {
|
||||
let user = user.clone();
|
||||
async move {
|
||||
|
||||
@@ -136,34 +136,10 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
};
|
||||
match ObjectId::from_str(&name_or_id) {
|
||||
Ok(_) => match resource_type {
|
||||
ResourceTargetVariant::Alerter => all_resources
|
||||
.alerters
|
||||
ResourceTargetVariant::Swarm => all_resources
|
||||
.swarms
|
||||
.get(&name_or_id)
|
||||
.map(|a| a.name.clone()),
|
||||
ResourceTargetVariant::Build => all_resources
|
||||
.builds
|
||||
.get(&name_or_id)
|
||||
.map(|b| b.name.clone()),
|
||||
ResourceTargetVariant::Builder => all_resources
|
||||
.builders
|
||||
.get(&name_or_id)
|
||||
.map(|b| b.name.clone()),
|
||||
ResourceTargetVariant::Deployment => all_resources
|
||||
.deployments
|
||||
.get(&name_or_id)
|
||||
.map(|d| d.name.clone()),
|
||||
ResourceTargetVariant::Procedure => all_resources
|
||||
.procedures
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::Action => all_resources
|
||||
.actions
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::Repo => all_resources
|
||||
.repos
|
||||
.get(&name_or_id)
|
||||
.map(|r| r.name.clone()),
|
||||
.map(|s| s.name.clone()),
|
||||
ResourceTargetVariant::Server => all_resources
|
||||
.servers
|
||||
.get(&name_or_id)
|
||||
@@ -172,10 +148,38 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
.stacks
|
||||
.get(&name_or_id)
|
||||
.map(|s| s.name.clone()),
|
||||
ResourceTargetVariant::Deployment => all_resources
|
||||
.deployments
|
||||
.get(&name_or_id)
|
||||
.map(|d| d.name.clone()),
|
||||
ResourceTargetVariant::Build => all_resources
|
||||
.builds
|
||||
.get(&name_or_id)
|
||||
.map(|b| b.name.clone()),
|
||||
ResourceTargetVariant::Repo => all_resources
|
||||
.repos
|
||||
.get(&name_or_id)
|
||||
.map(|r| r.name.clone()),
|
||||
ResourceTargetVariant::Procedure => all_resources
|
||||
.procedures
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::Action => all_resources
|
||||
.actions
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::ResourceSync => all_resources
|
||||
.syncs
|
||||
.get(&name_or_id)
|
||||
.map(|s| s.name.clone()),
|
||||
ResourceTargetVariant::Builder => all_resources
|
||||
.builders
|
||||
.get(&name_or_id)
|
||||
.map(|b| b.name.clone()),
|
||||
ResourceTargetVariant::Alerter => all_resources
|
||||
.alerters
|
||||
.get(&name_or_id)
|
||||
.map(|a| a.name.clone()),
|
||||
ResourceTargetVariant::System => None,
|
||||
},
|
||||
Err(_) => Some(name_or_id),
|
||||
|
||||
@@ -46,8 +46,13 @@ impl Resolve<ReadArgs> for ListActions {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Action>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Action>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +69,10 @@ impl Resolve<ReadArgs> for ListFullActions {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Action>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -100,6 +108,7 @@ impl Resolve<ReadArgs> for GetActionsSummary {
|
||||
let actions = resource::list_full_for_user::<Action>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -9,14 +9,14 @@ use komodo_client::{
|
||||
GetAlert, GetAlertResponse, ListAlerts, ListAlertsResponse,
|
||||
},
|
||||
entities::{
|
||||
deployment::Deployment, server::Server, stack::Stack,
|
||||
sync::ResourceSync,
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server, stack::Stack, sync::ResourceSync,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config, permission::get_resource_ids_for_user,
|
||||
config::core_config, permission::list_resource_ids_for_user,
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
@@ -31,14 +31,29 @@ impl Resolve<ReadArgs> for ListAlerts {
|
||||
) -> serror::Result<ListAlertsResponse> {
|
||||
let mut query = self.query.unwrap_or_default();
|
||||
if !user.admin && !core_config().transparent_mode {
|
||||
let server_ids =
|
||||
get_resource_ids_for_user::<Server>(user).await?;
|
||||
let stack_ids =
|
||||
get_resource_ids_for_user::<Stack>(user).await?;
|
||||
let deployment_ids =
|
||||
get_resource_ids_for_user::<Deployment>(user).await?;
|
||||
let sync_ids =
|
||||
get_resource_ids_for_user::<ResourceSync>(user).await?;
|
||||
let (server_ids, stack_ids, deployment_ids, sync_ids) = tokio::try_join!(
|
||||
list_resource_ids_for_user::<Server>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
),
|
||||
list_resource_ids_for_user::<Stack>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
),
|
||||
list_resource_ids_for_user::<Deployment>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
),
|
||||
list_resource_ids_for_user::<ResourceSync>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
)?;
|
||||
// All of the vecs will be non-none if !admin and !transparent mode.
|
||||
query.extend(doc! {
|
||||
"$or": [
|
||||
{ "target.type": "Server", "target.id": { "$in": &server_ids } },
|
||||
|
||||
@@ -11,8 +11,10 @@ use komodo_client::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_all_tags, permission::get_check_permissions,
|
||||
resource, state::db_client,
|
||||
helpers::query::get_all_tags,
|
||||
permission::{get_check_permissions, list_resource_ids_for_user},
|
||||
resource,
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
@@ -44,8 +46,13 @@ impl Resolve<ReadArgs> for ListAlerters {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Alerter>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Alerter>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -62,7 +69,10 @@ impl Resolve<ReadArgs> for ListFullAlerters {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Alerter>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -74,9 +84,11 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetAlertersSummaryResponse> {
|
||||
let query = match resource::get_resource_object_ids_for_user::<
|
||||
Alerter,
|
||||
>(user)
|
||||
let query = match list_resource_ids_for_user::<Alerter>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
|
||||
@@ -54,8 +54,13 @@ impl Resolve<ReadArgs> for ListBuilds {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Build>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Build>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +77,10 @@ impl Resolve<ReadArgs> for ListFullBuilds {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Build>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -108,6 +116,7 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
@@ -271,7 +280,10 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
|
||||
@@ -11,8 +11,10 @@ use komodo_client::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_all_tags, permission::get_check_permissions,
|
||||
resource, state::db_client,
|
||||
helpers::query::get_all_tags,
|
||||
permission::{get_check_permissions, list_resource_ids_for_user},
|
||||
resource,
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
@@ -44,8 +46,13 @@ impl Resolve<ReadArgs> for ListBuilders {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Builder>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Builder>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -62,7 +69,10 @@ impl Resolve<ReadArgs> for ListFullBuilders {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Builder>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -74,9 +84,11 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetBuildersSummaryResponse> {
|
||||
let query = match resource::get_resource_object_ids_for_user::<
|
||||
Builder,
|
||||
>(user)
|
||||
let query = match list_resource_ids_for_user::<Builder>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
|
||||
@@ -56,7 +56,10 @@ impl Resolve<ReadArgs> for ListDeployments {
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let deployments = resource::list_for_user::<Deployment>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
let deployments = if only_update_available {
|
||||
@@ -83,7 +86,10 @@ impl Resolve<ReadArgs> for ListFullDeployments {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -297,6 +303,7 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
@@ -342,7 +349,10 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
|
||||
@@ -11,6 +11,7 @@ use komodo_client::{
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
config::{DockerRegistry, GitProvider},
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
sync::ResourceSync,
|
||||
@@ -48,8 +49,10 @@ mod repo;
|
||||
mod schedule;
|
||||
mod server;
|
||||
mod stack;
|
||||
mod swarm;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod terminal;
|
||||
mod toml;
|
||||
mod update;
|
||||
mod user;
|
||||
@@ -73,36 +76,27 @@ enum ReadRequest {
|
||||
ListGitProvidersFromConfig(ListGitProvidersFromConfig),
|
||||
ListDockerRegistriesFromConfig(ListDockerRegistriesFromConfig),
|
||||
|
||||
// ==== USER ====
|
||||
GetUsername(GetUsername),
|
||||
GetPermission(GetPermission),
|
||||
FindUser(FindUser),
|
||||
ListUsers(ListUsers),
|
||||
ListApiKeys(ListApiKeys),
|
||||
ListApiKeysForServiceUser(ListApiKeysForServiceUser),
|
||||
ListPermissions(ListPermissions),
|
||||
ListUserTargetPermissions(ListUserTargetPermissions),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
GetUserGroup(GetUserGroup),
|
||||
ListUserGroups(ListUserGroups),
|
||||
|
||||
// ==== PROCEDURE ====
|
||||
GetProceduresSummary(GetProceduresSummary),
|
||||
GetProcedure(GetProcedure),
|
||||
GetProcedureActionState(GetProcedureActionState),
|
||||
ListProcedures(ListProcedures),
|
||||
ListFullProcedures(ListFullProcedures),
|
||||
|
||||
// ==== ACTION ====
|
||||
GetActionsSummary(GetActionsSummary),
|
||||
GetAction(GetAction),
|
||||
GetActionActionState(GetActionActionState),
|
||||
ListActions(ListActions),
|
||||
ListFullActions(ListFullActions),
|
||||
|
||||
// ==== SCHEDULE ====
|
||||
ListSchedules(ListSchedules),
|
||||
// ==== SWARM ====
|
||||
GetSwarmsSummary(GetSwarmsSummary),
|
||||
GetSwarm(GetSwarm),
|
||||
GetSwarmActionState(GetSwarmActionState),
|
||||
ListSwarms(ListSwarms),
|
||||
InspectSwarm(InspectSwarm),
|
||||
ListFullSwarms(ListFullSwarms),
|
||||
ListSwarmNodes(ListSwarmNodes),
|
||||
InspectSwarmNode(InspectSwarmNode),
|
||||
ListSwarmConfigs(ListSwarmConfigs),
|
||||
InspectSwarmConfig(InspectSwarmConfig),
|
||||
ListSwarmSecrets(ListSwarmSecrets),
|
||||
InspectSwarmSecret(InspectSwarmSecret),
|
||||
ListSwarmStacks(ListSwarmStacks),
|
||||
InspectSwarmStack(InspectSwarmStack),
|
||||
ListSwarmTasks(ListSwarmTasks),
|
||||
InspectSwarmTask(InspectSwarmTask),
|
||||
ListSwarmServices(ListSwarmServices),
|
||||
InspectSwarmService(InspectSwarmService),
|
||||
GetSwarmServiceLog(GetSwarmServiceLog),
|
||||
SearchSwarmServiceLog(SearchSwarmServiceLog),
|
||||
|
||||
// ==== SERVER ====
|
||||
GetServersSummary(GetServersSummary),
|
||||
@@ -110,11 +104,11 @@ enum ReadRequest {
|
||||
GetServerState(GetServerState),
|
||||
GetPeripheryInformation(GetPeripheryInformation),
|
||||
GetServerActionState(GetServerActionState),
|
||||
GetHistoricalServerStats(GetHistoricalServerStats),
|
||||
ListServers(ListServers),
|
||||
ListFullServers(ListFullServers),
|
||||
|
||||
// ==== TERMINAL ====
|
||||
ListTerminals(ListTerminals),
|
||||
ListAllTerminals(ListAllTerminals),
|
||||
|
||||
// ==== DOCKER ====
|
||||
GetDockerContainersSummary(GetDockerContainersSummary),
|
||||
@@ -136,6 +130,7 @@ enum ReadRequest {
|
||||
// ==== SERVER STATS ====
|
||||
GetSystemInformation(GetSystemInformation),
|
||||
GetSystemStats(GetSystemStats),
|
||||
GetHistoricalServerStats(GetHistoricalServerStats),
|
||||
ListSystemProcesses(ListSystemProcesses),
|
||||
|
||||
// ==== STACK ====
|
||||
@@ -181,6 +176,23 @@ enum ReadRequest {
|
||||
ListRepos(ListRepos),
|
||||
ListFullRepos(ListFullRepos),
|
||||
|
||||
// ==== PROCEDURE ====
|
||||
GetProceduresSummary(GetProceduresSummary),
|
||||
GetProcedure(GetProcedure),
|
||||
GetProcedureActionState(GetProcedureActionState),
|
||||
ListProcedures(ListProcedures),
|
||||
ListFullProcedures(ListFullProcedures),
|
||||
|
||||
// ==== ACTION ====
|
||||
GetActionsSummary(GetActionsSummary),
|
||||
GetAction(GetAction),
|
||||
GetActionActionState(GetActionActionState),
|
||||
ListActions(ListActions),
|
||||
ListFullActions(ListFullActions),
|
||||
|
||||
// ==== SCHEDULE ====
|
||||
ListSchedules(ListSchedules),
|
||||
|
||||
// ==== SYNC ====
|
||||
GetResourceSyncsSummary(GetResourceSyncsSummary),
|
||||
GetResourceSync(GetResourceSync),
|
||||
@@ -208,6 +220,20 @@ enum ReadRequest {
|
||||
GetTag(GetTag),
|
||||
ListTags(ListTags),
|
||||
|
||||
// ==== USER ====
|
||||
GetUsername(GetUsername),
|
||||
GetPermission(GetPermission),
|
||||
FindUser(FindUser),
|
||||
ListUsers(ListUsers),
|
||||
ListApiKeys(ListApiKeys),
|
||||
ListApiKeysForServiceUser(ListApiKeysForServiceUser),
|
||||
ListPermissions(ListPermissions),
|
||||
ListUserTargetPermissions(ListUserTargetPermissions),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
GetUserGroup(GetUserGroup),
|
||||
ListUserGroups(ListUserGroups),
|
||||
|
||||
// ==== UPDATE ====
|
||||
GetUpdate(GetUpdate),
|
||||
ListUpdates(ListUpdates),
|
||||
@@ -398,16 +424,19 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
)?;
|
||||
|
||||
@@ -45,7 +45,10 @@ impl Resolve<ReadArgs> for ListProcedures {
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Procedure>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -64,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullProcedures {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Procedure>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -79,6 +85,7 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
|
||||
let procedures = resource::list_full_for_user::<Procedure>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -44,8 +44,13 @@ impl Resolve<ReadArgs> for ListRepos {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Repo>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Repo>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -62,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullRepos {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Repo>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -98,6 +106,7 @@ impl Resolve<ReadArgs> for GetReposSummary {
|
||||
let repos = resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -4,6 +4,7 @@ use komodo_client::{
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
action::Action,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
resource::{ResourceQuery, TemplatesQueryBehavior},
|
||||
schedule::Schedule,
|
||||
@@ -35,6 +36,7 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
specific: Default::default(),
|
||||
},
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
),
|
||||
list_full_for_user::<Procedure>(
|
||||
@@ -46,6 +48,7 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
specific: Default::default(),
|
||||
},
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
)?;
|
||||
|
||||
+90
-158
@@ -12,7 +12,6 @@ use database::mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOptions},
|
||||
};
|
||||
use futures_util::{StreamExt as _, stream::FuturesUnordered};
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
@@ -26,11 +25,10 @@ use komodo_client::{
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
server::{
|
||||
Server, ServerActionState, ServerListItem, ServerState,
|
||||
TerminalInfo, TerminalInfoWithServer,
|
||||
Server, ServerActionState, ServerListItem, ServerQuery,
|
||||
ServerState,
|
||||
},
|
||||
stack::{Stack, StackServiceNames},
|
||||
stats::{SystemInformation, SystemProcess},
|
||||
@@ -51,7 +49,7 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
helpers::{periphery_client, query::get_all_tags},
|
||||
permission::get_check_permissions,
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
resource,
|
||||
stack::compose_container_match_regex,
|
||||
state::{action_states, db_client, server_status_cache},
|
||||
@@ -67,6 +65,7 @@ impl Resolve<ReadArgs> for GetServersSummary {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
@@ -127,8 +126,13 @@ impl Resolve<ReadArgs> for ListServers {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Server>(self.query, user, &all_tags)
|
||||
.await?,
|
||||
resource::list_for_user::<Server>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -145,7 +149,10 @@ impl Resolve<ReadArgs> for ListFullServers {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -376,8 +383,8 @@ impl Resolve<ReadArgs> for ListDockerContainers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(containers) = &cache.containers {
|
||||
Ok(containers.clone())
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.containers.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -390,17 +397,12 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListAllDockerContainersResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
ServerQuery::builder().names(self.servers.clone()).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|server| {
|
||||
self.servers.is_empty()
|
||||
|| self.servers.contains(&server.id)
|
||||
|| self.servers.contains(&server.name)
|
||||
});
|
||||
.await?;
|
||||
|
||||
let mut containers = Vec::<ContainerListItem>::new();
|
||||
|
||||
@@ -408,9 +410,18 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(more_containers) = &cache.containers {
|
||||
containers.extend(more_containers.clone());
|
||||
}
|
||||
let Some(docker) = &cache.docker else {
|
||||
continue;
|
||||
};
|
||||
let more = docker
|
||||
.containers
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
self.containers.is_empty()
|
||||
|| self.containers.contains(&container.name)
|
||||
})
|
||||
.cloned();
|
||||
containers.extend(more);
|
||||
}
|
||||
|
||||
Ok(containers)
|
||||
@@ -425,6 +436,7 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
@@ -437,8 +449,8 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(containers) = &cache.containers {
|
||||
for container in containers {
|
||||
if let Some(docker) = &cache.docker {
|
||||
for container in &docker.containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
@@ -576,12 +588,12 @@ impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
}
|
||||
|
||||
// then check stacks
|
||||
let stacks =
|
||||
resource::list_full_for_user_using_document::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
user,
|
||||
)
|
||||
.await?;
|
||||
let stacks = list_resources_for_user::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check matching stack
|
||||
for stack in stacks {
|
||||
@@ -630,8 +642,8 @@ impl Resolve<ReadArgs> for ListDockerNetworks {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(networks) = &cache.networks {
|
||||
Ok(networks.clone())
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.networks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -683,8 +695,8 @@ impl Resolve<ReadArgs> for ListDockerImages {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(images) = &cache.images {
|
||||
Ok(images.clone())
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.images.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -764,8 +776,8 @@ impl Resolve<ReadArgs> for ListDockerVolumes {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(volumes) = &cache.volumes {
|
||||
Ok(volumes.clone())
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.volumes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -814,134 +826,54 @@ impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(projects) = &cache.projects {
|
||||
Ok(projects.clone())
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.projects.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TerminalCacheItem {
|
||||
list: Vec<TerminalInfo>,
|
||||
ttl: i64,
|
||||
}
|
||||
// impl Resolve<ReadArgs> for ListAllTerminals {
|
||||
// async fn resolve(
|
||||
// self,
|
||||
// args: &ReadArgs,
|
||||
// ) -> Result<Self::Response, Self::Error> {
|
||||
// // match self.tar
|
||||
// let mut terminals = resource::list_full_for_user::<Server>(
|
||||
// self.query, &args.user, &all_tags,
|
||||
// )
|
||||
// .await?
|
||||
// .into_iter()
|
||||
// .map(|server| async move {
|
||||
// (
|
||||
// list_terminals_inner(&server, self.fresh).await,
|
||||
// (server.id, server.name),
|
||||
// )
|
||||
// })
|
||||
// .collect::<FuturesUnordered<_>>()
|
||||
// .collect::<Vec<_>>()
|
||||
// .await
|
||||
// .into_iter()
|
||||
// .flat_map(|(terminals, server)| {
|
||||
// let terminals = terminals.ok()?;
|
||||
// Some((terminals, server))
|
||||
// })
|
||||
// .flat_map(|(terminals, (server_id, server_name))| {
|
||||
// terminals.into_iter().map(move |info| {
|
||||
// TerminalInfoWithServer::from_terminal_info(
|
||||
// &server_id,
|
||||
// &server_name,
|
||||
// info,
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
const TERMINAL_CACHE_TIMEOUT: i64 = 30_000;
|
||||
// terminals.sort_by(|a, b| {
|
||||
// a.server_name.cmp(&b.server_name).then(a.name.cmp(&b.name))
|
||||
// });
|
||||
|
||||
#[derive(Default)]
|
||||
struct TerminalCache(
|
||||
std::sync::Mutex<
|
||||
HashMap<String, Arc<tokio::sync::Mutex<TerminalCacheItem>>>,
|
||||
>,
|
||||
);
|
||||
|
||||
impl TerminalCache {
|
||||
fn get_or_insert(
|
||||
&self,
|
||||
server_id: String,
|
||||
) -> Arc<tokio::sync::Mutex<TerminalCacheItem>> {
|
||||
if let Some(cached) =
|
||||
self.0.lock().unwrap().get(&server_id).cloned()
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
let to_cache =
|
||||
Arc::new(tokio::sync::Mutex::new(TerminalCacheItem::default()));
|
||||
self.0.lock().unwrap().insert(server_id, to_cache.clone());
|
||||
to_cache
|
||||
}
|
||||
}
|
||||
|
||||
fn terminals_cache() -> &'static TerminalCache {
|
||||
static TERMINALS: OnceLock<TerminalCache> = OnceLock::new();
|
||||
TERMINALS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListTerminals {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListTerminalsResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_inner(&server, self.fresh)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllTerminals {
|
||||
async fn resolve(
|
||||
self,
|
||||
args: &ReadArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let mut terminals = resource::list_full_for_user::<Server>(
|
||||
self.query, &args.user, &all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|server| async move {
|
||||
(
|
||||
list_terminals_inner(&server, self.fresh).await,
|
||||
(server.id, server.name),
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(terminals, server)| {
|
||||
let terminals = terminals.ok()?;
|
||||
Some((terminals, server))
|
||||
})
|
||||
.flat_map(|(terminals, (server_id, server_name))| {
|
||||
terminals.into_iter().map(move |info| {
|
||||
TerminalInfoWithServer::from_terminal_info(
|
||||
&server_id,
|
||||
&server_name,
|
||||
info,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
terminals.sort_by(|a, b| {
|
||||
a.server_name.cmp(&b.server_name).then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
Ok(terminals)
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_terminals_inner(
|
||||
server: &Server,
|
||||
fresh: bool,
|
||||
) -> anyhow::Result<Vec<TerminalInfo>> {
|
||||
let cache = terminals_cache().get_or_insert(server.id.clone());
|
||||
let mut cache = cache.lock().await;
|
||||
if fresh || komodo_timestamp() > cache.ttl {
|
||||
cache.list = periphery_client(server)
|
||||
.await?
|
||||
.request(periphery_client::api::terminal::ListTerminals {
|
||||
container: None,
|
||||
})
|
||||
.await
|
||||
.context("Failed to get fresh terminal list")?;
|
||||
cache.ttl = komodo_timestamp() + TERMINAL_CACHE_TIMEOUT;
|
||||
Ok(cache.list.clone())
|
||||
} else {
|
||||
Ok(cache.list.clone())
|
||||
}
|
||||
}
|
||||
// Ok(terminals)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -200,7 +200,10 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
@@ -231,7 +234,10 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
@@ -262,9 +268,13 @@ impl Resolve<ReadArgs> for ListStacks {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let stacks =
|
||||
resource::list_for_user::<Stack>(self.query, user, &all_tags)
|
||||
.await?;
|
||||
let stacks = resource::list_for_user::<Stack>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
let stacks = if only_update_available {
|
||||
stacks
|
||||
.into_iter()
|
||||
@@ -295,7 +305,10 @@ impl Resolve<ReadArgs> for ListFullStacks {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Stack>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -331,6 +344,7 @@ impl Resolve<ReadArgs> for GetStacksSummary {
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
use anyhow::Context;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::{query::get_all_tags, swarm::swarm_request},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
state::{action_states, swarm_status_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarm {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<Swarm> {
|
||||
Ok(
|
||||
get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarms {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<Vec<SwarmListItem>> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Swarm>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListFullSwarms {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListFullSwarmsResponse> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Swarm>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarmActionState {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<SwarmActionState> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let action_state = action_states()
|
||||
.swarm
|
||||
.get(&swarm.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?;
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarmsSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetSwarmsSummaryResponse> {
|
||||
let swarms = resource::list_full_for_user::<Swarm>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get swarms from db")?;
|
||||
|
||||
let mut res = GetSwarmsSummaryResponse::default();
|
||||
|
||||
let cache = swarm_status_cache();
|
||||
|
||||
for swarm in swarms {
|
||||
res.total += 1;
|
||||
|
||||
match cache
|
||||
.get(&swarm.id)
|
||||
.await
|
||||
.map(|status| status.state)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
SwarmState::Unknown => {
|
||||
res.unknown += 1;
|
||||
}
|
||||
SwarmState::Healthy => {
|
||||
res.healthy += 1;
|
||||
}
|
||||
SwarmState::Unhealthy => {
|
||||
res.unhealthy += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarm {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
let inspect = cache
|
||||
.inspect
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.context("SwarmInspectInfo not available")?;
|
||||
Ok(inspect)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmNodes {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmNodesResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.nodes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmNode {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmNodeResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmNode {
|
||||
node: self.node,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmServices {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmServicesResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.services.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmService {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmServiceResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmService {
|
||||
service: self.service,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarmServiceLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetSwarmServiceLogResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLog {
|
||||
service: self.service,
|
||||
tail: self.tail,
|
||||
timestamps: self.timestamps,
|
||||
no_task_ids: self.no_task_ids,
|
||||
no_resolve: self.no_resolve,
|
||||
details: self.details,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchSwarmServiceLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<SearchSwarmServiceLogResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLogSearch {
|
||||
service: self.service,
|
||||
terms: self.terms,
|
||||
combinator: self.combinator,
|
||||
invert: self.invert,
|
||||
timestamps: self.timestamps,
|
||||
no_task_ids: self.no_task_ids,
|
||||
no_resolve: self.no_resolve,
|
||||
details: self.details,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmTasks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmTasksResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.tasks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmTask {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmTaskResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmTask {
|
||||
task: self.task,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmSecrets {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmSecretsResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.secrets.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmSecret {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmSecretResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmSecret {
|
||||
secret: self.secret,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmConfigs {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmConfigsResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.configs.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmConfig {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmConfigResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmConfig {
|
||||
config: self.config,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListSwarmStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListSwarmStacksResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache =
|
||||
swarm_status_cache().get_or_insert_default(&swarm.id).await;
|
||||
if let Some(lists) = &cache.lists {
|
||||
Ok(lists.stacks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectSwarmStack {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<InspectSwarmStackResponse> {
|
||||
let swarm = get_check_permissions::<Swarm>(
|
||||
&self.swarm,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmStack {
|
||||
stack: self.stack,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,10 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -64,7 +67,10 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
self.query, user, &all_tags,
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -101,6 +107,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
use anyhow::Context as _;
|
||||
use futures_util::{
|
||||
FutureExt, StreamExt as _, stream::FuturesUnordered,
|
||||
};
|
||||
use komodo_client::{
|
||||
api::read::{ListTerminals, ListTerminalsResponse},
|
||||
entities::{
|
||||
deployment::Deployment,
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{Terminal, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client, permission::get_check_permissions,
|
||||
resource,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<ReadArgs> for ListTerminals {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<ListTerminalsResponse> {
|
||||
let Some(target) = self.target else {
|
||||
return list_all_terminals_for_user(user, self.use_names).await;
|
||||
};
|
||||
match &target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_all_terminals_for_user(
|
||||
user: &User,
|
||||
use_names: bool,
|
||||
) -> serror::Result<Vec<Terminal>> {
|
||||
let (mut servers, stacks, deployments) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
)
|
||||
.map(|res| res.map(|servers| servers
|
||||
.into_iter()
|
||||
// true denotes user actually has permission on this Server.
|
||||
.map(|server| (server, true))
|
||||
.collect::<Vec<_>>())),
|
||||
resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
),
|
||||
)?;
|
||||
|
||||
// Ensure any missing servers are present to query
|
||||
for stack in &stacks {
|
||||
if !stack.config.server_id.is_empty()
|
||||
&& !servers
|
||||
.iter()
|
||||
.any(|(server, _)| server.id == stack.config.server_id)
|
||||
{
|
||||
let server =
|
||||
resource::get::<Server>(&stack.config.server_id).await?;
|
||||
servers.push((server, false));
|
||||
}
|
||||
}
|
||||
for deployment in &deployments {
|
||||
if !deployment.config.server_id.is_empty()
|
||||
&& !servers
|
||||
.iter()
|
||||
.any(|(server, _)| server.id == deployment.config.server_id)
|
||||
{
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
servers.push((server, false));
|
||||
}
|
||||
}
|
||||
|
||||
let mut terminals = servers
|
||||
.into_iter()
|
||||
.map(|(server, server_permission)| async move {
|
||||
(
|
||||
list_terminals_on_server(&server, None).await,
|
||||
(server.id, server.name, server_permission),
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(
|
||||
|(terminals, (server_id, server_name, server_permission))| {
|
||||
let terminals = terminals
|
||||
.ok()?
|
||||
.into_iter()
|
||||
.filter_map(|mut terminal| {
|
||||
// Only keep terminals with appropriate perms.
|
||||
match terminal.target.clone() {
|
||||
TerminalTarget::Server { .. } => server_permission
|
||||
.then(|| {
|
||||
terminal.target = TerminalTarget::Server {
|
||||
server: Some(if use_names {
|
||||
server_name.clone()
|
||||
} else {
|
||||
server_id.clone()
|
||||
}),
|
||||
};
|
||||
terminal
|
||||
}),
|
||||
TerminalTarget::Container { container, .. } => {
|
||||
server_permission.then(|| {
|
||||
terminal.target = TerminalTarget::Container {
|
||||
server: if use_names {
|
||||
server_name.clone()
|
||||
} else {
|
||||
server_id.clone()
|
||||
},
|
||||
container,
|
||||
};
|
||||
terminal
|
||||
})
|
||||
}
|
||||
TerminalTarget::Stack { stack, service } => {
|
||||
stacks.iter().find(|s| s.id == stack).map(|s| {
|
||||
terminal.target = TerminalTarget::Stack {
|
||||
stack: if use_names {
|
||||
s.name.clone()
|
||||
} else {
|
||||
s.id.clone()
|
||||
},
|
||||
service,
|
||||
};
|
||||
terminal
|
||||
})
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
deployments.iter().find(|d| d.id == deployment).map(
|
||||
|d| {
|
||||
terminal.target = TerminalTarget::Deployment {
|
||||
deployment: if use_names {
|
||||
d.name.clone()
|
||||
} else {
|
||||
d.id.clone()
|
||||
},
|
||||
};
|
||||
terminal
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(terminals)
|
||||
},
|
||||
)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
terminals.sort_by(|a, b| {
|
||||
a.target.cmp(&b.target).then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
Ok(terminals)
|
||||
}
|
||||
|
||||
async fn list_terminals_on_server(
|
||||
server: &Server,
|
||||
target: Option<TerminalTarget>,
|
||||
) -> serror::Result<Vec<Terminal>> {
|
||||
periphery_client(server)
|
||||
.await?
|
||||
.request(periphery_client::api::terminal::ListTerminals {
|
||||
target,
|
||||
})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to get Terminal list from Server {} ({})",
|
||||
server.name, server.id
|
||||
)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -11,7 +11,8 @@ use komodo_client::{
|
||||
builder::Builder, deployment::Deployment,
|
||||
permission::PermissionLevel, procedure::Procedure, repo::Repo,
|
||||
resource::ResourceQuery, server::Server, stack::Stack,
|
||||
sync::ResourceSync, toml::ResourcesToml, user::User,
|
||||
swarm::Swarm, sync::ResourceSync, toml::ResourcesToml,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
@@ -46,6 +47,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Alerter>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -56,6 +58,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Builder>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -66,6 +69,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Server>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -76,6 +80,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Stack>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -86,6 +91,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -96,6 +102,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Build>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -106,6 +113,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Repo>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -116,6 +124,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Procedure>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -126,6 +135,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<Action>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -136,6 +146,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -197,42 +208,21 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
let ReadArgs { user } = args;
|
||||
for target in targets {
|
||||
match target {
|
||||
ResourceTarget::Alerter(id) => {
|
||||
let mut alerter = get_check_permissions::<Alerter>(
|
||||
ResourceTarget::Swarm(id) => {
|
||||
let mut swarm = get_check_permissions::<Swarm>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Alerter::replace_ids(&mut alerter);
|
||||
res.alerters.push(convert_resource::<Alerter>(
|
||||
alerter,
|
||||
Swarm::replace_ids(&mut swarm);
|
||||
res.swarms.push(convert_resource::<Swarm>(
|
||||
swarm,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
let mut sync = get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
if sync.config.file_contents.is_empty()
|
||||
&& (sync.config.files_on_host
|
||||
|| !sync.config.repo.is_empty()
|
||||
|| !sync.config.linked_repo.is_empty())
|
||||
{
|
||||
ResourceSync::replace_ids(&mut sync);
|
||||
res.resource_syncs.push(convert_resource::<ResourceSync>(
|
||||
sync,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
let mut server = get_check_permissions::<Server>(
|
||||
&id,
|
||||
@@ -248,31 +238,16 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
let mut builder = get_check_permissions::<Builder>(
|
||||
ResourceTarget::Stack(id) => {
|
||||
let mut stack = get_check_permissions::<Stack>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Builder::replace_ids(&mut builder);
|
||||
res.builders.push(convert_resource::<Builder>(
|
||||
builder,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
let mut build = get_check_permissions::<Build>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Build::replace_ids(&mut build);
|
||||
res.builds.push(convert_resource::<Build>(
|
||||
build,
|
||||
Stack::replace_ids(&mut stack);
|
||||
res.stacks.push(convert_resource::<Stack>(
|
||||
stack,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
@@ -293,6 +268,21 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
let mut build = get_check_permissions::<Build>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Build::replace_ids(&mut build);
|
||||
res.builds.push(convert_resource::<Build>(
|
||||
build,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
let mut repo = get_check_permissions::<Repo>(
|
||||
&id,
|
||||
@@ -308,21 +298,6 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
let mut stack = get_check_permissions::<Stack>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Stack::replace_ids(&mut stack);
|
||||
res.stacks.push(convert_resource::<Stack>(
|
||||
stack,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
let mut procedure = get_check_permissions::<Procedure>(
|
||||
&id,
|
||||
@@ -353,6 +328,57 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
&id_to_tags,
|
||||
));
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
let mut sync = get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
if sync.config.file_contents.is_empty()
|
||||
&& (sync.config.files_on_host
|
||||
|| !sync.config.repo.is_empty()
|
||||
|| !sync.config.linked_repo.is_empty())
|
||||
{
|
||||
ResourceSync::replace_ids(&mut sync);
|
||||
res.resource_syncs.push(convert_resource::<ResourceSync>(
|
||||
sync,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
let mut builder = get_check_permissions::<Builder>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Builder::replace_ids(&mut builder);
|
||||
res.builders.push(convert_resource::<Builder>(
|
||||
builder,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
let mut alerter = get_check_permissions::<Alerter>(
|
||||
&id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
Alerter::replace_ids(&mut alerter);
|
||||
res.alerters.push(convert_resource::<Alerter>(
|
||||
alerter,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::System(_) => continue,
|
||||
};
|
||||
}
|
||||
|
||||
+154
-107
@@ -20,6 +20,7 @@ use komodo_client::{
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
update::{Update, UpdateListItem},
|
||||
user::User,
|
||||
@@ -29,7 +30,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
permission::{get_check_permissions, get_resource_ids_for_user},
|
||||
permission::{get_check_permissions, list_resource_ids_for_user},
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
@@ -45,99 +46,137 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
let query = if user.admin || core_config().transparent_mode {
|
||||
self.query
|
||||
} else {
|
||||
let server_query = get_resource_ids_for_user::<Server>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Server", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Server" });
|
||||
|
||||
let deployment_query =
|
||||
get_resource_ids_for_user::<Deployment>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Deployment", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
|
||||
|
||||
let stack_query = get_resource_ids_for_user::<Stack>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Stack", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Stack" });
|
||||
|
||||
let build_query = get_resource_ids_for_user::<Build>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Build", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Build" });
|
||||
|
||||
let repo_query = get_resource_ids_for_user::<Repo>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Repo", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Repo" });
|
||||
|
||||
let procedure_query =
|
||||
get_resource_ids_for_user::<Procedure>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Procedure", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
let action_query = get_resource_ids_for_user::<Action>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Action", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
|
||||
let builder_query = get_resource_ids_for_user::<Builder>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Builder", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Builder" });
|
||||
|
||||
let alerter_query = get_resource_ids_for_user::<Alerter>(user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Alerter", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
|
||||
|
||||
let resource_sync_query = get_resource_ids_for_user::<
|
||||
ResourceSync,
|
||||
>(user)
|
||||
let server_query = list_resource_ids_for_user::<Server>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "ResourceSync", "target.id": { "$in": ids }
|
||||
"target.type": "Server", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
|
||||
.unwrap_or_else(|| doc! { "target.type": "Server" });
|
||||
|
||||
let deployment_query =
|
||||
list_resource_ids_for_user::<Deployment>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Deployment", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
|
||||
|
||||
let stack_query = list_resource_ids_for_user::<Stack>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Stack", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Stack" });
|
||||
|
||||
let build_query = list_resource_ids_for_user::<Build>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Build", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Build" });
|
||||
|
||||
let repo_query = list_resource_ids_for_user::<Repo>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Repo", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Repo" });
|
||||
|
||||
let procedure_query = list_resource_ids_for_user::<Procedure>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Procedure", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
let action_query = list_resource_ids_for_user::<Action>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Action", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
|
||||
let builder_query = list_resource_ids_for_user::<Builder>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Builder", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Builder" });
|
||||
|
||||
let alerter_query = list_resource_ids_for_user::<Alerter>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Alerter", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
|
||||
|
||||
let resource_sync_query =
|
||||
list_resource_ids_for_user::<ResourceSync>(
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "ResourceSync", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
|
||||
|
||||
let mut query = self.query.unwrap_or_default();
|
||||
query.extend(doc! {
|
||||
@@ -228,6 +267,14 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
anyhow!("user must be admin to view system updates").into(),
|
||||
);
|
||||
}
|
||||
ResourceTarget::Swarm(id) => {
|
||||
get_check_permissions::<Swarm>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
get_check_permissions::<Server>(
|
||||
id,
|
||||
@@ -236,6 +283,14 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
get_check_permissions::<Stack>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
get_check_permissions::<Deployment>(
|
||||
id,
|
||||
@@ -260,22 +315,6 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
get_check_permissions::<Builder>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
get_check_permissions::<Alerter>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
get_check_permissions::<Procedure>(
|
||||
id,
|
||||
@@ -300,8 +339,16 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
get_check_permissions::<Stack>(
|
||||
ResourceTarget::Builder(id) => {
|
||||
get_check_permissions::<Builder>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
get_check_permissions::<Alerter>(
|
||||
id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
use anyhow::Context;
|
||||
use axum::{Extension, Router, middleware, routing::post};
|
||||
use komodo_client::{
|
||||
api::{terminal::*, write::TerminalRecreateMode},
|
||||
entities::{
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server, stack::Stack, user::User,
|
||||
},
|
||||
};
|
||||
use periphery_client::api::terminal::CreateTerminal;
|
||||
use komodo_client::{api::terminal::*, entities::user::User};
|
||||
use serror::Json;
|
||||
|
||||
use crate::{
|
||||
auth::auth_request, helpers::periphery_client,
|
||||
permission::get_check_permissions, resource::get,
|
||||
state::stack_status_cache,
|
||||
auth::auth_request, helpers::terminal::setup_target_for_user,
|
||||
};
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/execute", post(execute_terminal))
|
||||
.route("/execute/container", post(execute_container_exec))
|
||||
.route("/execute/deployment", post(execute_deployment_exec))
|
||||
.route("/execute/stack", post(execute_stack_exec))
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
|
||||
@@ -34,14 +22,15 @@ pub fn router() -> Router {
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server,
|
||||
target,
|
||||
terminal,
|
||||
init = format!("{init:?}")
|
||||
)
|
||||
)]
|
||||
async fn execute_terminal(
|
||||
Extension(user): Extension<User>,
|
||||
Json(ExecuteTerminalBody {
|
||||
server,
|
||||
target,
|
||||
terminal,
|
||||
command,
|
||||
init,
|
||||
@@ -49,200 +38,13 @@ async fn execute_terminal(
|
||||
) -> serror::Result<axum::body::Body> {
|
||||
info!("/terminal/execute request | user: {}", user.username);
|
||||
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
// Maybe init terminal.
|
||||
if let Some(InitTerminal { command, recreate }) = init {
|
||||
periphery
|
||||
.request(CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
command,
|
||||
recreate,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
let (target, terminal, periphery) =
|
||||
setup_target_for_user(target, terminal, init, &user).await?;
|
||||
|
||||
let stream = periphery
|
||||
.execute_terminal(terminal, command)
|
||||
.execute_terminal(target, terminal, command)
|
||||
.await
|
||||
.context("Failed to execute command on periphery")?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(stream))
|
||||
}
|
||||
|
||||
// ======================
|
||||
// ExecuteContainerExec
|
||||
// ======================
|
||||
|
||||
#[instrument(
|
||||
name = "ExecuteContainerExec",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server,
|
||||
container,
|
||||
shell,
|
||||
recreate = format!("{recreate:?}"),
|
||||
)
|
||||
)]
|
||||
async fn execute_container_exec(
|
||||
Extension(user): Extension<User>,
|
||||
Json(ExecuteContainerExecBody {
|
||||
server,
|
||||
container,
|
||||
shell,
|
||||
command,
|
||||
recreate,
|
||||
}): Json<ExecuteContainerExecBody>,
|
||||
) -> serror::Result<axum::body::Body> {
|
||||
info!("ExecuteContainerExec request | user: {}", user.username);
|
||||
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let stream = periphery
|
||||
.execute_container_exec(
|
||||
container,
|
||||
shell,
|
||||
command,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to execute container exec command on periphery",
|
||||
)?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(stream))
|
||||
}
|
||||
|
||||
// =======================
|
||||
// ExecuteDeploymentExec
|
||||
// =======================
|
||||
|
||||
#[instrument(
|
||||
name = "ExecuteDeploymentExec",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
deployment,
|
||||
shell,
|
||||
recreate = format!("{recreate:?}"),
|
||||
)
|
||||
)]
|
||||
async fn execute_deployment_exec(
|
||||
Extension(user): Extension<User>,
|
||||
Json(ExecuteDeploymentExecBody {
|
||||
deployment,
|
||||
shell,
|
||||
command,
|
||||
recreate,
|
||||
}): Json<ExecuteDeploymentExecBody>,
|
||||
) -> serror::Result<axum::body::Body> {
|
||||
info!("ExecuteDeploymentExec request | user: {}", user.username);
|
||||
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
&deployment,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server = get::<Server>(&deployment.config.server_id).await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let stream = periphery
|
||||
.execute_container_exec(
|
||||
deployment.name,
|
||||
shell,
|
||||
command,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to execute container exec command on periphery",
|
||||
)?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(stream))
|
||||
}
|
||||
|
||||
// ==================
|
||||
// ExecuteStackExec
|
||||
// ==================
|
||||
|
||||
#[instrument(
|
||||
name = "ExecuteStackExec",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
stack,
|
||||
service,
|
||||
shell,
|
||||
recreate = format!("{recreate:?}"),
|
||||
)
|
||||
)]
|
||||
async fn execute_stack_exec(
|
||||
Extension(user): Extension<User>,
|
||||
Json(ExecuteStackExecBody {
|
||||
stack,
|
||||
service,
|
||||
shell,
|
||||
command,
|
||||
recreate,
|
||||
}): Json<ExecuteStackExecBody>,
|
||||
) -> serror::Result<axum::body::Body> {
|
||||
info!("ExecuteStackExec request | user: {}", user.username);
|
||||
|
||||
let stack = get_check_permissions::<Stack>(
|
||||
&stack,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server = get::<Server>(&stack.config.server_id).await?;
|
||||
|
||||
let container = stack_status_cache()
|
||||
.get(&stack.id)
|
||||
.await
|
||||
.context("could not get stack status")?
|
||||
.curr
|
||||
.services
|
||||
.iter()
|
||||
.find(|s| s.service == service)
|
||||
.context("could not find service")?
|
||||
.container
|
||||
.as_ref()
|
||||
.context("could not find service container")?
|
||||
.name
|
||||
.clone();
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let stream = periphery
|
||||
.execute_container_exec(
|
||||
container,
|
||||
shell,
|
||||
command,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to execute container exec command on periphery",
|
||||
)?;
|
||||
.context("Failed to execute command on Terminal")?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(stream))
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ mod resource;
|
||||
mod server;
|
||||
mod service_user;
|
||||
mod stack;
|
||||
mod swarm;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod terminal;
|
||||
mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
@@ -52,36 +54,16 @@ pub struct WriteArgs {
|
||||
#[error(serror::Error)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
pub enum WriteRequest {
|
||||
// ==== USER ====
|
||||
CreateLocalUser(CreateLocalUser),
|
||||
UpdateUserUsername(UpdateUserUsername),
|
||||
UpdateUserPassword(UpdateUserPassword),
|
||||
DeleteUser(DeleteUser),
|
||||
|
||||
// ==== SERVICE USER ====
|
||||
CreateServiceUser(CreateServiceUser),
|
||||
UpdateServiceUserDescription(UpdateServiceUserDescription),
|
||||
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
|
||||
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
CreateUserGroup(CreateUserGroup),
|
||||
RenameUserGroup(RenameUserGroup),
|
||||
DeleteUserGroup(DeleteUserGroup),
|
||||
AddUserToUserGroup(AddUserToUserGroup),
|
||||
RemoveUserFromUserGroup(RemoveUserFromUserGroup),
|
||||
SetUsersInUserGroup(SetUsersInUserGroup),
|
||||
SetEveryoneUserGroup(SetEveryoneUserGroup),
|
||||
|
||||
// ==== PERMISSIONS ====
|
||||
UpdateUserAdmin(UpdateUserAdmin),
|
||||
UpdateUserBasePermissions(UpdateUserBasePermissions),
|
||||
UpdatePermissionOnResourceType(UpdatePermissionOnResourceType),
|
||||
UpdatePermissionOnTarget(UpdatePermissionOnTarget),
|
||||
|
||||
// ==== RESOURCE ====
|
||||
UpdateResourceMeta(UpdateResourceMeta),
|
||||
|
||||
// ==== SWARM ====
|
||||
CreateSwarm(CreateSwarm),
|
||||
CopySwarm(CopySwarm),
|
||||
DeleteSwarm(DeleteSwarm),
|
||||
UpdateSwarm(UpdateSwarm),
|
||||
RenameSwarm(RenameSwarm),
|
||||
|
||||
// ==== SERVER ====
|
||||
CreateServer(CreateServer),
|
||||
CopyServer(CopyServer),
|
||||
@@ -89,12 +71,14 @@ pub enum WriteRequest {
|
||||
UpdateServer(UpdateServer),
|
||||
RenameServer(RenameServer),
|
||||
CreateNetwork(CreateNetwork),
|
||||
UpdateServerPublicKey(UpdateServerPublicKey),
|
||||
RotateServerKeys(RotateServerKeys),
|
||||
|
||||
// ==== TERMINAL ====
|
||||
CreateTerminal(CreateTerminal),
|
||||
DeleteTerminal(DeleteTerminal),
|
||||
DeleteAllTerminals(DeleteAllTerminals),
|
||||
BatchDeleteAllTerminals(BatchDeleteAllTerminals),
|
||||
UpdateServerPublicKey(UpdateServerPublicKey),
|
||||
RotateServerKeys(RotateServerKeys),
|
||||
|
||||
// ==== STACK ====
|
||||
CreateStack(CreateStack),
|
||||
@@ -122,13 +106,6 @@ pub enum WriteRequest {
|
||||
WriteBuildFileContents(WriteBuildFileContents),
|
||||
RefreshBuildCache(RefreshBuildCache),
|
||||
|
||||
// ==== BUILDER ====
|
||||
CreateBuilder(CreateBuilder),
|
||||
CopyBuilder(CopyBuilder),
|
||||
DeleteBuilder(DeleteBuilder),
|
||||
UpdateBuilder(UpdateBuilder),
|
||||
RenameBuilder(RenameBuilder),
|
||||
|
||||
// ==== REPO ====
|
||||
CreateRepo(CreateRepo),
|
||||
CopyRepo(CopyRepo),
|
||||
@@ -137,13 +114,6 @@ pub enum WriteRequest {
|
||||
RenameRepo(RenameRepo),
|
||||
RefreshRepoCache(RefreshRepoCache),
|
||||
|
||||
// ==== ALERTER ====
|
||||
CreateAlerter(CreateAlerter),
|
||||
CopyAlerter(CopyAlerter),
|
||||
DeleteAlerter(DeleteAlerter),
|
||||
UpdateAlerter(UpdateAlerter),
|
||||
RenameAlerter(RenameAlerter),
|
||||
|
||||
// ==== PROCEDURE ====
|
||||
CreateProcedure(CreateProcedure),
|
||||
CopyProcedure(CopyProcedure),
|
||||
@@ -168,6 +138,52 @@ pub enum WriteRequest {
|
||||
CommitSync(CommitSync),
|
||||
RefreshResourceSyncPending(RefreshResourceSyncPending),
|
||||
|
||||
// ==== BUILDER ====
|
||||
CreateBuilder(CreateBuilder),
|
||||
CopyBuilder(CopyBuilder),
|
||||
DeleteBuilder(DeleteBuilder),
|
||||
UpdateBuilder(UpdateBuilder),
|
||||
RenameBuilder(RenameBuilder),
|
||||
|
||||
// ==== ALERTER ====
|
||||
CreateAlerter(CreateAlerter),
|
||||
CopyAlerter(CopyAlerter),
|
||||
DeleteAlerter(DeleteAlerter),
|
||||
UpdateAlerter(UpdateAlerter),
|
||||
RenameAlerter(RenameAlerter),
|
||||
|
||||
// ==== ONBOARDING KEY ====
|
||||
CreateOnboardingKey(CreateOnboardingKey),
|
||||
UpdateOnboardingKey(UpdateOnboardingKey),
|
||||
DeleteOnboardingKey(DeleteOnboardingKey),
|
||||
|
||||
// ==== USER ====
|
||||
CreateLocalUser(CreateLocalUser),
|
||||
UpdateUserUsername(UpdateUserUsername),
|
||||
UpdateUserPassword(UpdateUserPassword),
|
||||
DeleteUser(DeleteUser),
|
||||
|
||||
// ==== SERVICE USER ====
|
||||
CreateServiceUser(CreateServiceUser),
|
||||
UpdateServiceUserDescription(UpdateServiceUserDescription),
|
||||
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
|
||||
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
CreateUserGroup(CreateUserGroup),
|
||||
RenameUserGroup(RenameUserGroup),
|
||||
DeleteUserGroup(DeleteUserGroup),
|
||||
AddUserToUserGroup(AddUserToUserGroup),
|
||||
RemoveUserFromUserGroup(RemoveUserFromUserGroup),
|
||||
SetUsersInUserGroup(SetUsersInUserGroup),
|
||||
SetEveryoneUserGroup(SetEveryoneUserGroup),
|
||||
|
||||
// ==== PERMISSIONS ====
|
||||
UpdateUserAdmin(UpdateUserAdmin),
|
||||
UpdateUserBasePermissions(UpdateUserBasePermissions),
|
||||
UpdatePermissionOnResourceType(UpdatePermissionOnResourceType),
|
||||
UpdatePermissionOnTarget(UpdatePermissionOnTarget),
|
||||
|
||||
// ==== TAG ====
|
||||
CreateTag(CreateTag),
|
||||
DeleteTag(DeleteTag),
|
||||
@@ -189,11 +205,6 @@ pub enum WriteRequest {
|
||||
UpdateDockerRegistryAccount(UpdateDockerRegistryAccount),
|
||||
DeleteDockerRegistryAccount(DeleteDockerRegistryAccount),
|
||||
|
||||
// ==== ONBOARDING KEY ====
|
||||
CreateOnboardingKey(CreateOnboardingKey),
|
||||
UpdateOnboardingKey(UpdateOnboardingKey),
|
||||
DeleteOnboardingKey(DeleteOnboardingKey),
|
||||
|
||||
// ==== ALERT ====
|
||||
CloseAlert(CloseAlert),
|
||||
}
|
||||
|
||||
@@ -307,8 +307,8 @@ async fn extract_user_target_with_validation(
|
||||
.users
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for users")?
|
||||
.context("no matching user found")?
|
||||
.context("Failed to query db for users")?
|
||||
.context("No matching user found")?
|
||||
.id;
|
||||
Ok((UserTargetVariant::User, id))
|
||||
}
|
||||
@@ -321,8 +321,8 @@ async fn extract_user_target_with_validation(
|
||||
.user_groups
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for user_groups")?
|
||||
.context("no matching user_group found")?
|
||||
.context("Failed to query db for user_groups")?
|
||||
.context("No matching user_group found")?
|
||||
.id;
|
||||
Ok((UserTargetVariant::UserGroup, id))
|
||||
}
|
||||
@@ -338,47 +338,19 @@ async fn extract_resource_target_with_validation(
|
||||
let res = resource_target.extract_variant_id();
|
||||
Ok((res.0, res.1.clone()))
|
||||
}
|
||||
ResourceTarget::Build(ident) => {
|
||||
ResourceTarget::Swarm(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.builds
|
||||
.swarms
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for builds")?
|
||||
.context("no matching build found")?
|
||||
.context("Failed to query db for swarms")?
|
||||
.context("No matching server found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Build, id))
|
||||
}
|
||||
ResourceTarget::Builder(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.builders
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for builders")?
|
||||
.context("no matching builder found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Builder, id))
|
||||
}
|
||||
ResourceTarget::Deployment(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.deployments
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for deployments")?
|
||||
.context("no matching deployment found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Deployment, id))
|
||||
Ok((ResourceTargetVariant::Server, id))
|
||||
}
|
||||
ResourceTarget::Server(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
@@ -389,11 +361,53 @@ async fn extract_resource_target_with_validation(
|
||||
.servers
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for servers")?
|
||||
.context("no matching server found")?
|
||||
.context("Failed to query db for servers")?
|
||||
.context("No matching server found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Server, id))
|
||||
}
|
||||
ResourceTarget::Stack(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.stacks
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for stacks")?
|
||||
.context("No matching stack found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Stack, id))
|
||||
}
|
||||
ResourceTarget::Deployment(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.deployments
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for deployments")?
|
||||
.context("No matching deployment found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Deployment, id))
|
||||
}
|
||||
ResourceTarget::Build(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.builds
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for builds")?
|
||||
.context("No matching build found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Build, id))
|
||||
}
|
||||
ResourceTarget::Repo(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
@@ -403,25 +417,11 @@ async fn extract_resource_target_with_validation(
|
||||
.repos
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for repos")?
|
||||
.context("no matching repo found")?
|
||||
.context("Failed to query db for repos")?
|
||||
.context("No matching repo found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Repo, id))
|
||||
}
|
||||
ResourceTarget::Alerter(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.alerters
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for alerters")?
|
||||
.context("no matching alerter found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Alerter, id))
|
||||
}
|
||||
ResourceTarget::Procedure(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
@@ -431,8 +431,8 @@ async fn extract_resource_target_with_validation(
|
||||
.procedures
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for procedures")?
|
||||
.context("no matching procedure found")?
|
||||
.context("Failed to query db for procedures")?
|
||||
.context("No matching procedure found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Procedure, id))
|
||||
}
|
||||
@@ -445,8 +445,8 @@ async fn extract_resource_target_with_validation(
|
||||
.actions
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for actions")?
|
||||
.context("no matching action found")?
|
||||
.context("Failed to query db for actions")?
|
||||
.context("No matching action found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Action, id))
|
||||
}
|
||||
@@ -459,24 +459,38 @@ async fn extract_resource_target_with_validation(
|
||||
.resource_syncs
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for resource syncs")?
|
||||
.context("no matching resource sync found")?
|
||||
.context("Failed to query db for resource syncs")?
|
||||
.context("No matching resource sync found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::ResourceSync, id))
|
||||
}
|
||||
ResourceTarget::Stack(ident) => {
|
||||
ResourceTarget::Builder(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.stacks
|
||||
.builders
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for stacks")?
|
||||
.context("no matching stack found")?
|
||||
.context("Failed to query db for builders")?
|
||||
.context("No matching builder found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Stack, id))
|
||||
Ok((ResourceTargetVariant::Builder, id))
|
||||
}
|
||||
ResourceTarget::Alerter(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.alerters
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for alerters")?
|
||||
.context("No matching alerter found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Alerter, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ use komodo_client::{
|
||||
entities::{
|
||||
ResourceTarget, action::Action, alerter::Alerter, build::Build,
|
||||
builder::Builder, deployment::Deployment, procedure::Procedure,
|
||||
repo::Repo, server::Server, stack::Stack, sync::ResourceSync,
|
||||
repo::Repo, server::Server, stack::Stack, swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
@@ -45,9 +46,15 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
|
||||
.status_code(StatusCode::BAD_REQUEST),
|
||||
);
|
||||
}
|
||||
ResourceTarget::Swarm(id) => {
|
||||
resource::update_meta::<Swarm>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
resource::update_meta::<Server>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
resource::update_meta::<Stack>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
resource::update_meta::<Deployment>(&id, meta, args).await?;
|
||||
}
|
||||
@@ -57,12 +64,6 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
|
||||
ResourceTarget::Repo(id) => {
|
||||
resource::update_meta::<Repo>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
resource::update_meta::<Builder>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
resource::update_meta::<Alerter>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
resource::update_meta::<Procedure>(&id, meta, args).await?;
|
||||
}
|
||||
@@ -73,8 +74,11 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
|
||||
resource::update_meta::<ResourceSync>(&id, meta, args)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
resource::update_meta::<Stack>(&id, meta, args).await?;
|
||||
ResourceTarget::Builder(id) => {
|
||||
resource::update_meta::<Builder>(&id, meta, args).await?;
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
resource::update_meta::<Alerter>(&id, meta, args).await?;
|
||||
}
|
||||
}
|
||||
Ok(UpdateResourceMetaResponse {})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, format_serror};
|
||||
use futures_util::{StreamExt, stream::FuturesUnordered};
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
NoData, Operation,
|
||||
Operation,
|
||||
permission::PermissionLevel,
|
||||
server::{Server, ServerInfo},
|
||||
to_docker_compatible_name,
|
||||
@@ -17,7 +16,6 @@ use resolver_api::Resolve;
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::get_all_tags,
|
||||
update::{add_update, make_update, update_update},
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
@@ -189,162 +187,6 @@ impl Resolve<WriteArgs> for CreateNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CreateTerminal {
|
||||
#[instrument(
|
||||
"CreateTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server = self.server,
|
||||
terminal = self.name,
|
||||
command = self.command,
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Write.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::CreateTerminal {
|
||||
name: self.name,
|
||||
command: self.command,
|
||||
recreate: self.recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create terminal on Periphery")?;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
#[instrument(
|
||||
"DeleteTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server = self.server,
|
||||
terminal = self.terminal,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Write.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteTerminal {
|
||||
terminal: self.terminal,
|
||||
})
|
||||
.await
|
||||
.context("Failed to delete terminal on Periphery")?;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteAllTerminals {
|
||||
#[instrument(
|
||||
"DeleteAllTerminals",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server = self.server,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Write.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteAllTerminals {})
|
||||
.await
|
||||
.context("Failed to delete all terminals on Periphery")?;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for BatchDeleteAllTerminals {
|
||||
#[instrument(
|
||||
"BatchDeleteAllTerminals",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
query = format!("{:?}", self.query),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query, user, &all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|server| async move {
|
||||
let res = async {
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteAllTerminals {})
|
||||
.await
|
||||
.context("Failed to delete all terminals on Periphery")?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"Failed to delete all terminals on {} ({}) | {e:#}",
|
||||
server.name, server.id
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for UpdateServerPublicKey {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
permission::PermissionLevel, swarm::Swarm, update::Update,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{permission::get_check_permissions, resource};
|
||||
|
||||
use super::WriteArgs;
|
||||
|
||||
impl Resolve<WriteArgs> for CreateSwarm {
|
||||
#[instrument(
|
||||
"CreateSwarm",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
swarm = self.name,
|
||||
config = serde_json::to_string(&self.config).unwrap(),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Swarm> {
|
||||
resource::create::<Swarm>(&self.name, self.config, None, user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CopySwarm {
|
||||
#[instrument(
|
||||
"CopySwarm",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
swarm = self.name,
|
||||
copy_swarm = self.id,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Swarm> {
|
||||
let Swarm { config, .. } = get_check_permissions::<Swarm>(
|
||||
&self.id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
resource::create::<Swarm>(&self.name, config.into(), None, user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteSwarm {
|
||||
#[instrument(
|
||||
"DeleteSwarm",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
swarm = self.id,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Swarm> {
|
||||
Ok(resource::delete::<Swarm>(&self.id, user).await?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for UpdateSwarm {
|
||||
#[instrument(
|
||||
"UpdateSwarm",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
swarm = self.id,
|
||||
update = serde_json::to_string(&self.config).unwrap()
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Swarm> {
|
||||
Ok(resource::update::<Swarm>(&self.id, self.config, user).await?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for RenameSwarm {
|
||||
#[instrument(
|
||||
"RenameSwarm",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
swarm = self.id,
|
||||
new_name = self.name
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Update> {
|
||||
Ok(resource::rename::<Swarm>(&self.id, &self.name, user).await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
use anyhow::Context as _;
|
||||
use futures_util::{StreamExt as _, stream::FuturesUnordered};
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
NoData, deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server, stack::Stack, terminal::TerminalTarget,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use periphery_client::api;
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::get_all_tags,
|
||||
terminal::{
|
||||
create_container_terminal_inner,
|
||||
get_deployment_periphery_container,
|
||||
get_stack_service_periphery_container,
|
||||
},
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
};
|
||||
|
||||
use super::WriteArgs;
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for CreateTerminal {
|
||||
#[instrument(
|
||||
"CreateTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
terminal = self.name,
|
||||
target = format!("{:?}", self.target),
|
||||
command = self.command,
|
||||
mode = format!("{:?}", self.mode),
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
match self.target.clone() {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
create_server_terminal(self, server, user).await?;
|
||||
}
|
||||
TerminalTarget::Container { server, container } => {
|
||||
create_container_terminal(self, server, container, user)
|
||||
.await?;
|
||||
}
|
||||
TerminalTarget::Stack { stack, service } => {
|
||||
let service = service
|
||||
.context("Must provide 'target.params.service'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
create_stack_service_terminal(self, stack, service, user)
|
||||
.await?;
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
create_deployment_terminal(self, deployment, user).await?;
|
||||
}
|
||||
};
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_server_terminal(
|
||||
CreateTerminal {
|
||||
name,
|
||||
command,
|
||||
recreate,
|
||||
target: _,
|
||||
mode: _,
|
||||
}: CreateTerminal,
|
||||
server: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::CreateServerTerminal {
|
||||
name,
|
||||
command,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create Server Terminal on Periphery")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_container_terminal(
|
||||
req: CreateTerminal,
|
||||
server: String,
|
||||
container: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
let periphery = periphery_client(&server).await?;
|
||||
create_container_terminal_inner(req, &periphery, container).await
|
||||
}
|
||||
|
||||
async fn create_stack_service_terminal(
|
||||
req: CreateTerminal,
|
||||
stack: String,
|
||||
service: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, periphery, container) =
|
||||
get_stack_service_periphery_container(&stack, &service, user)
|
||||
.await?;
|
||||
create_container_terminal_inner(req, &periphery, container).await
|
||||
}
|
||||
|
||||
async fn create_deployment_terminal(
|
||||
req: CreateTerminal,
|
||||
deployment: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, periphery, container) =
|
||||
get_deployment_periphery_container(&deployment, user).await?;
|
||||
create_container_terminal_inner(req, &periphery, container).await
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
#[instrument(
|
||||
"DeleteTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
target = format!("{:?}", self.target),
|
||||
terminal = self.terminal,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
let server = match &self.target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
}
|
||||
};
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteTerminal {
|
||||
target: self.target,
|
||||
terminal: self.terminal,
|
||||
})
|
||||
.await
|
||||
.context("Failed to delete terminal on Periphery")?;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteAllTerminals {
|
||||
#[instrument(
|
||||
"DeleteAllTerminals",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
server = self.server,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<NoData> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteAllTerminals {})
|
||||
.await
|
||||
.context("Failed to delete all terminals on Periphery")?;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for BatchDeleteAllTerminals {
|
||||
#[instrument(
|
||||
"BatchDeleteAllTerminals",
|
||||
skip_all,
|
||||
fields(
|
||||
operator = user.id,
|
||||
query = format!("{:?}", self.query),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|server| async move {
|
||||
let res = async {
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
.request(api::terminal::DeleteAllTerminals {})
|
||||
.await
|
||||
.context("Failed to delete all terminals on Periphery")?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"Failed to delete all terminals on {} ({}) | {e:#}",
|
||||
server.name, server.id
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use jsonwebtoken::{DecodingKey, Validation, decode};
|
||||
use jsonwebtoken::dangerous::insecure_decode;
|
||||
use komodo_client::entities::{
|
||||
config::core::{CoreConfig, OauthCredentials},
|
||||
random_string,
|
||||
@@ -138,15 +138,8 @@ impl GoogleOauthClient {
|
||||
&self,
|
||||
id_token: &str,
|
||||
) -> anyhow::Result<GoogleUser> {
|
||||
let mut v = Validation::new(Default::default());
|
||||
v.insecure_disable_signature_validation();
|
||||
v.validate_aud = false;
|
||||
let res = decode::<GoogleUser>(
|
||||
id_token,
|
||||
&DecodingKey::from_secret(b""),
|
||||
&v,
|
||||
)
|
||||
.context("failed to decode google id token")?;
|
||||
let res = insecure_decode::<GoogleUser>(id_token)
|
||||
.context("failed to decode google id token")?;
|
||||
Ok(res.claims)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ impl PeripheryConnectionArgs<'_> {
|
||||
periphery_connections().insert(id.clone(), self).await;
|
||||
|
||||
let responses = connection.responses.clone();
|
||||
let terminals = connection.terminals.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
@@ -91,11 +90,7 @@ impl PeripheryConnectionArgs<'_> {
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PeripheryClient {
|
||||
id,
|
||||
responses,
|
||||
terminals,
|
||||
})
|
||||
Ok(PeripheryClient { id, responses })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ use transport::{
|
||||
},
|
||||
channel::{BufferedReceiver, Sender, buffered_channel},
|
||||
websocket::{
|
||||
Websocket, WebsocketMessage, WebsocketReceiver as _,
|
||||
Websocket, WebsocketReceiver as _, WebsocketReceiverExt,
|
||||
WebsocketSender as _,
|
||||
},
|
||||
};
|
||||
@@ -367,8 +367,22 @@ impl PeripheryConnection {
|
||||
|
||||
let forward_writes = async {
|
||||
loop {
|
||||
let Ok(message) = receiver.recv().await else {
|
||||
break;
|
||||
let message = match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
receiver.recv(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => message,
|
||||
Ok(Err(_)) => break,
|
||||
// Handle sending Ping
|
||||
Err(_) => {
|
||||
if let Err(e) = ws_write.ping().await {
|
||||
self.set_error(e).await;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match ws_write.send(message.into_bytes()).await {
|
||||
Ok(_) => receiver.clear_buffer(),
|
||||
@@ -385,19 +399,13 @@ impl PeripheryConnection {
|
||||
|
||||
let handle_reads = async {
|
||||
loop {
|
||||
match ws_read.recv().await {
|
||||
Ok(WebsocketMessage::Message(message)) => {
|
||||
self.handle_incoming_message(message).await
|
||||
}
|
||||
Ok(WebsocketMessage::Close(_))
|
||||
| Ok(WebsocketMessage::Closed) => {
|
||||
self.set_error(anyhow!("Connection closed")).await;
|
||||
break;
|
||||
}
|
||||
match ws_read.recv_message().await {
|
||||
Ok(message) => self.handle_incoming_message(message).await,
|
||||
Err(e) => {
|
||||
self.set_error(e).await;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Cancel again if not already
|
||||
cancel.cancel();
|
||||
@@ -410,15 +418,8 @@ impl PeripheryConnection {
|
||||
|
||||
pub async fn handle_incoming_message(
|
||||
&self,
|
||||
message: EncodedTransportMessage,
|
||||
message: TransportMessage,
|
||||
) {
|
||||
let message: TransportMessage = match message.decode() {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse Message bytes | {e:#}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match message {
|
||||
TransportMessage::Response(data) => {
|
||||
match data.decode().map(ResponseMessage::into_inner) {
|
||||
|
||||
@@ -9,12 +9,13 @@ use komodo_client::{
|
||||
deployment::DeploymentActionState,
|
||||
procedure::ProcedureActionState, repo::RepoActionState,
|
||||
server::ServerActionState, stack::StackActionState,
|
||||
sync::ResourceSyncActionState,
|
||||
swarm::SwarmActionState, sync::ResourceSyncActionState,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ActionStates {
|
||||
pub swarm: CloneCache<String, Arc<ActionState<SwarmActionState>>>,
|
||||
pub server: CloneCache<String, Arc<ActionState<ServerActionState>>>,
|
||||
pub stack: CloneCache<String, Arc<ActionState<StackActionState>>>,
|
||||
pub deployment:
|
||||
|
||||
@@ -3,11 +3,12 @@ use std::collections::HashMap;
|
||||
use komodo_client::entities::{
|
||||
action::Action, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, stack::Stack, sync::ResourceSync,
|
||||
server::Server, stack::Stack, swarm::Swarm, sync::ResourceSync,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AllResourcesById {
|
||||
pub swarms: HashMap<String, Swarm>,
|
||||
pub servers: HashMap<String, Server>,
|
||||
pub deployments: HashMap<String, Deployment>,
|
||||
pub stacks: HashMap<String, Stack>,
|
||||
@@ -27,6 +28,10 @@ impl AllResourcesById {
|
||||
let id_to_tags = ↦
|
||||
let match_tags = &[];
|
||||
Ok(Self {
|
||||
swarms: crate::resource::get_id_to_resource_map::<Swarm>(
|
||||
id_to_tags, match_tags,
|
||||
)
|
||||
.await?,
|
||||
servers: crate::resource::get_id_to_resource_map::<Server>(
|
||||
id_to_tags, match_tags,
|
||||
)
|
||||
|
||||
@@ -30,6 +30,8 @@ pub mod matcher;
|
||||
pub mod procedure;
|
||||
pub mod prune;
|
||||
pub mod query;
|
||||
pub mod swarm;
|
||||
pub mod terminal;
|
||||
pub mod update;
|
||||
|
||||
// pub mod resource;
|
||||
|
||||
@@ -10,6 +10,7 @@ use komodo_client::{
|
||||
action::Action,
|
||||
build::Build,
|
||||
deployment::Deployment,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
stack::Stack,
|
||||
@@ -1311,6 +1312,7 @@ async fn extend_batch_exection<E: ExtendBatch>(
|
||||
pattern,
|
||||
Default::default(),
|
||||
procedure_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?
|
||||
|
||||
@@ -25,6 +25,7 @@ use komodo_client::{
|
||||
repo::Repo,
|
||||
server::{Server, ServerState},
|
||||
stack::{Stack, StackServiceNames, StackState},
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::Update,
|
||||
@@ -287,24 +288,24 @@ pub async fn get_user_permission_on_target(
|
||||
) -> anyhow::Result<PermissionLevelAndSpecifics> {
|
||||
match target {
|
||||
ResourceTarget::System(_) => Ok(PermissionLevel::None.into()),
|
||||
ResourceTarget::Build(id) => {
|
||||
get_user_permission_on_resource::<Build>(user, id).await
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
get_user_permission_on_resource::<Builder>(user, id).await
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
get_user_permission_on_resource::<Deployment>(user, id).await
|
||||
ResourceTarget::Swarm(id) => {
|
||||
get_user_permission_on_resource::<Swarm>(user, id).await
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
get_user_permission_on_resource::<Server>(user, id).await
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
get_user_permission_on_resource::<Stack>(user, id).await
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
get_user_permission_on_resource::<Deployment>(user, id).await
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
get_user_permission_on_resource::<Build>(user, id).await
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
get_user_permission_on_resource::<Repo>(user, id).await
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
get_user_permission_on_resource::<Alerter>(user, id).await
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
get_user_permission_on_resource::<Procedure>(user, id).await
|
||||
}
|
||||
@@ -314,8 +315,11 @@ pub async fn get_user_permission_on_target(
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
get_user_permission_on_resource::<ResourceSync>(user, id).await
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
get_user_permission_on_resource::<Stack>(user, id).await
|
||||
ResourceTarget::Builder(id) => {
|
||||
get_user_permission_on_resource::<Builder>(user, id).await
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
get_user_permission_on_resource::<Alerter>(user, id).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use komodo_client::entities::server::{Server, ServerState};
|
||||
use resolver_api::HasResponse;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client, resource, state::server_status_cache,
|
||||
};
|
||||
|
||||
pub async fn swarm_request<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
swarm_request_custom_timeout(
|
||||
server_ids,
|
||||
request,
|
||||
Duration::from_secs(10),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn swarm_request_custom_timeout<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
let status_cache = server_status_cache();
|
||||
let mut err = Option::<anyhow::Error>::None;
|
||||
for server_id in server_ids {
|
||||
let Some(ServerState::Ok) =
|
||||
status_cache.get(server_id).await.map(|status| status.state)
|
||||
else {
|
||||
err = Some(anyhow!("No managers connected"));
|
||||
continue;
|
||||
};
|
||||
let Ok(periphery) =
|
||||
(match resource::get::<Server>(server_id).await {
|
||||
Ok(server) => periphery_client(&server).await,
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
continue;
|
||||
}
|
||||
})
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match periphery
|
||||
.request_custom_timeout(request.clone(), timeout)
|
||||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
}
|
||||
Err(
|
||||
err
|
||||
.unwrap_or_else(|| anyhow!("Unknown error"))
|
||||
.context("Failed to request from Swarm"),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use komodo_client::{
|
||||
api::{terminal::InitTerminal, write::CreateTerminal},
|
||||
entities::{
|
||||
deployment::Deployment,
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{ContainerTerminalMode, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use periphery_client::api;
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client, periphery::PeripheryClient,
|
||||
permission::get_check_permissions, resource,
|
||||
state::stack_status_cache,
|
||||
};
|
||||
|
||||
pub async fn setup_target_for_user(
|
||||
target: TerminalTarget,
|
||||
terminal: Option<String>,
|
||||
init: Option<InitTerminal>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, String, PeripheryClient)> {
|
||||
match target {
|
||||
TerminalTarget::Server { server } => {
|
||||
setup_server_target_for_user(
|
||||
server.context("Missing 'target.params.server'")?,
|
||||
terminal,
|
||||
init,
|
||||
user,
|
||||
)
|
||||
.await
|
||||
}
|
||||
TerminalTarget::Container { server, container } => {
|
||||
setup_container_target_for_user(
|
||||
server, container, terminal, init, user,
|
||||
)
|
||||
.await
|
||||
}
|
||||
TerminalTarget::Stack { stack, service } => {
|
||||
setup_stack_service_target_for_user(
|
||||
stack,
|
||||
service.context("Missing 'target.params.service'")?,
|
||||
terminal,
|
||||
init,
|
||||
user,
|
||||
)
|
||||
.await
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
setup_deployment_target_for_user(
|
||||
deployment, terminal, init, user,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_server_target_for_user(
|
||||
server: String,
|
||||
terminal: Option<String>,
|
||||
init: Option<InitTerminal>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, String, PeripheryClient)> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let terminal = terminal.unwrap_or_else(|| {
|
||||
init
|
||||
.as_ref()
|
||||
.and_then(|init| init.command.clone())
|
||||
.unwrap_or_else(|| String::from("term"))
|
||||
});
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
if let Some(init) = init {
|
||||
periphery
|
||||
.request(api::terminal::CreateServerTerminal {
|
||||
name: terminal.clone(),
|
||||
command: init.command,
|
||||
recreate: init.recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create Server Terminal on Periphery")?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
TerminalTarget::Server {
|
||||
server: Some(server.id),
|
||||
},
|
||||
terminal,
|
||||
periphery,
|
||||
))
|
||||
}
|
||||
|
||||
async fn setup_container_target_for_user(
|
||||
server: String,
|
||||
container: String,
|
||||
terminal: Option<String>,
|
||||
init: Option<InitTerminal>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, String, PeripheryClient)> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let terminal = default_container_terminal_name(
|
||||
terminal,
|
||||
&container,
|
||||
init.as_ref(),
|
||||
);
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let target = TerminalTarget::Container {
|
||||
server: server.id,
|
||||
container: container.clone(),
|
||||
};
|
||||
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
recreate: init.recreate,
|
||||
},
|
||||
&periphery,
|
||||
container,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((target, terminal, periphery))
|
||||
}
|
||||
|
||||
async fn setup_stack_service_target_for_user(
|
||||
stack: String,
|
||||
service: String,
|
||||
terminal: Option<String>,
|
||||
init: Option<InitTerminal>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, String, PeripheryClient)> {
|
||||
let (target, periphery, container) =
|
||||
get_stack_service_periphery_container(&stack, &service, user)
|
||||
.await?;
|
||||
|
||||
let terminal = default_container_terminal_name(
|
||||
terminal,
|
||||
&container,
|
||||
init.as_ref(),
|
||||
);
|
||||
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
recreate: init.recreate,
|
||||
},
|
||||
&periphery,
|
||||
container,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((target, terminal, periphery))
|
||||
}
|
||||
|
||||
async fn setup_deployment_target_for_user(
|
||||
deployment: String,
|
||||
terminal: Option<String>,
|
||||
init: Option<InitTerminal>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, String, PeripheryClient)> {
|
||||
let (target, periphery, container) =
|
||||
get_deployment_periphery_container(&deployment, user).await?;
|
||||
|
||||
let terminal = default_container_terminal_name(
|
||||
terminal,
|
||||
&container,
|
||||
init.as_ref(),
|
||||
);
|
||||
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
recreate: init.recreate,
|
||||
},
|
||||
&periphery,
|
||||
container,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((target, terminal, periphery))
|
||||
}
|
||||
|
||||
fn default_container_terminal_name(
|
||||
terminal: Option<String>,
|
||||
container: &str,
|
||||
init: Option<&InitTerminal>,
|
||||
) -> String {
|
||||
terminal.unwrap_or_else(|| {
|
||||
init
|
||||
.as_ref()
|
||||
.map(|init| {
|
||||
init.command.clone().unwrap_or_else(|| {
|
||||
init.mode.unwrap_or_default().as_ref().to_string()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| container.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name,
|
||||
target,
|
||||
command,
|
||||
mode,
|
||||
recreate,
|
||||
}: CreateTerminal,
|
||||
periphery: &PeripheryClient,
|
||||
container: String,
|
||||
) -> anyhow::Result<()> {
|
||||
match mode.unwrap_or_default() {
|
||||
ContainerTerminalMode::Exec => periphery
|
||||
.request(periphery_client::api::terminal::CreateContainerExecTerminal {
|
||||
name,
|
||||
target,
|
||||
container,
|
||||
command,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context(
|
||||
"Failed to create Container Exec Terminal on Periphery",
|
||||
)?,
|
||||
ContainerTerminalMode::Attach => periphery
|
||||
.request(periphery_client::api::terminal::CreateContainerAttachTerminal {
|
||||
name,
|
||||
target,
|
||||
container,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context(
|
||||
"Failed to create Container Attach Terminal on Periphery",
|
||||
)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_stack_service_periphery_container(
|
||||
stack: &str,
|
||||
service: &str,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, PeripheryClient, String)> {
|
||||
let stack = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server =
|
||||
resource::get::<Server>(&stack.config.server_id).await?;
|
||||
|
||||
let Some(status) = stack_status_cache().get(&stack.id).await else {
|
||||
return Err(anyhow!("Could not get Stack status"));
|
||||
};
|
||||
|
||||
let container = status
|
||||
.curr
|
||||
.services
|
||||
.iter()
|
||||
.find(|s| s.service.as_str() == service)
|
||||
.with_context(|| {
|
||||
format!("Did not find Stack service matching {service}")
|
||||
})?
|
||||
.container
|
||||
.as_ref()
|
||||
.with_context(|| {
|
||||
format!("Did not find container for Stack service {service}")
|
||||
})?
|
||||
.name
|
||||
.clone();
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
Ok((
|
||||
TerminalTarget::Stack {
|
||||
stack: stack.id,
|
||||
service: Some(service.to_string()),
|
||||
},
|
||||
periphery,
|
||||
container,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_deployment_periphery_container(
|
||||
deployment: &str,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(TerminalTarget, PeripheryClient, String)> {
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let container = deployment.name.clone();
|
||||
|
||||
Ok((
|
||||
TerminalTarget::Deployment {
|
||||
deployment: deployment.id,
|
||||
},
|
||||
periphery,
|
||||
container,
|
||||
))
|
||||
}
|
||||
@@ -43,6 +43,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
logger::init(&config.logging)?;
|
||||
|
||||
let startup_span = info_span!("CoreStartup");
|
||||
|
||||
async {
|
||||
info!("Komodo Core version: v{}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
@@ -75,7 +76,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
startup::on_startup().await;
|
||||
|
||||
// Spawn background tasks
|
||||
monitor::spawn_monitor_loop();
|
||||
monitor::spawn_monitoring_loops();
|
||||
resource::spawn_resource_refresh_loop();
|
||||
resource::spawn_all_resources_cache_refresh_loop();
|
||||
resource::spawn_build_state_refresh_loop();
|
||||
|
||||
@@ -2,7 +2,8 @@ use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
resource::ResourceQuery, server::Server, user::User,
|
||||
permission::PermissionLevel, resource::ResourceQuery,
|
||||
server::Server, user::system_user,
|
||||
};
|
||||
|
||||
use crate::resource;
|
||||
@@ -33,10 +34,8 @@ async fn get_all_servers_map()
|
||||
{
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
ResourceQuery::default(),
|
||||
&User {
|
||||
admin: true,
|
||||
..Default::default()
|
||||
},
|
||||
system_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,29 +1,73 @@
|
||||
use komodo_client::entities::{
|
||||
alert::SeverityLevel,
|
||||
deployment::{Deployment, DeploymentState},
|
||||
docker::{
|
||||
container::ContainerListItem, image::ImageListItem,
|
||||
network::NetworkListItem, volume::VolumeListItem,
|
||||
},
|
||||
docker::DockerLists,
|
||||
repo::Repo,
|
||||
server::{
|
||||
PeripheryInformation, Server, ServerConfig, ServerHealth,
|
||||
ServerHealthState, ServerState,
|
||||
},
|
||||
stack::{ComposeProject, Stack, StackState},
|
||||
stack::{Stack, StackState},
|
||||
stats::{SingleDiskUsage, SystemInformation, SystemStats},
|
||||
};
|
||||
use serror::Serror;
|
||||
|
||||
use crate::state::{
|
||||
deployment_status_cache, repo_status_cache, server_status_cache,
|
||||
stack_status_cache,
|
||||
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
|
||||
CachedStackStatus, History, deployment_status_cache,
|
||||
repo_status_cache, server_status_cache, stack_status_cache,
|
||||
};
|
||||
|
||||
use super::{
|
||||
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
|
||||
CachedStackStatus, History,
|
||||
};
|
||||
pub async fn insert_server_status(
|
||||
server: &Server,
|
||||
state: ServerState,
|
||||
periphery_info: Option<PeripheryInformation>,
|
||||
system_info: Option<SystemInformation>,
|
||||
system_stats: Option<SystemStats>,
|
||||
docker: Option<DockerLists>,
|
||||
err: impl Into<Option<Serror>>,
|
||||
) {
|
||||
let health =
|
||||
system_stats.as_ref().map(|s| get_server_health(server, s));
|
||||
server_status_cache()
|
||||
.insert(
|
||||
server.id.clone(),
|
||||
CachedServerStatus {
|
||||
id: server.id.clone(),
|
||||
state,
|
||||
periphery_info,
|
||||
system_info,
|
||||
system_stats,
|
||||
health,
|
||||
docker,
|
||||
err: err.into(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn insert_stacks_status_unknown(stacks: Vec<Stack>) {
|
||||
let status_cache = stack_status_cache();
|
||||
for stack in stacks {
|
||||
let prev =
|
||||
status_cache.get(&stack.id).await.map(|s| s.curr.state);
|
||||
status_cache
|
||||
.insert(
|
||||
stack.id.clone(),
|
||||
History {
|
||||
curr: CachedStackStatus {
|
||||
id: stack.id,
|
||||
state: StackState::Unknown,
|
||||
services: Vec::new(),
|
||||
},
|
||||
prev,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_deployments_status_unknown(
|
||||
deployments: Vec<Deployment>,
|
||||
@@ -66,69 +110,6 @@ pub async fn insert_repos_status_unknown(repos: Vec<Repo>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_stacks_status_unknown(stacks: Vec<Stack>) {
|
||||
let status_cache = stack_status_cache();
|
||||
for stack in stacks {
|
||||
let prev =
|
||||
status_cache.get(&stack.id).await.map(|s| s.curr.state);
|
||||
status_cache
|
||||
.insert(
|
||||
stack.id.clone(),
|
||||
History {
|
||||
curr: CachedStackStatus {
|
||||
id: stack.id,
|
||||
state: StackState::Unknown,
|
||||
services: Vec::new(),
|
||||
},
|
||||
prev,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
type DockerLists = (
|
||||
Option<Vec<ContainerListItem>>,
|
||||
Option<Vec<NetworkListItem>>,
|
||||
Option<Vec<ImageListItem>>,
|
||||
Option<Vec<VolumeListItem>>,
|
||||
Option<Vec<ComposeProject>>,
|
||||
);
|
||||
|
||||
pub async fn insert_server_status(
|
||||
server: &Server,
|
||||
state: ServerState,
|
||||
periphery_info: Option<PeripheryInformation>,
|
||||
system_info: Option<SystemInformation>,
|
||||
system_stats: Option<SystemStats>,
|
||||
(containers, networks, images, volumes, projects): DockerLists,
|
||||
err: impl Into<Option<Serror>>,
|
||||
) {
|
||||
let health =
|
||||
system_stats.as_ref().map(|s| get_server_health(server, s));
|
||||
server_status_cache()
|
||||
.insert(
|
||||
server.id.clone(),
|
||||
CachedServerStatus {
|
||||
id: server.id.clone(),
|
||||
state,
|
||||
periphery_info,
|
||||
system_info,
|
||||
system_stats,
|
||||
health,
|
||||
containers,
|
||||
networks,
|
||||
images,
|
||||
volumes,
|
||||
projects,
|
||||
err: err.into(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
const ALERT_PERCENTAGE_THRESHOLD: f32 = 5.0;
|
||||
|
||||
fn get_server_health(
|
||||
|
||||
+46
-84
@@ -7,19 +7,15 @@ use futures_util::future::join_all;
|
||||
use helpers::insert_stacks_status_unknown;
|
||||
use komodo_client::entities::{
|
||||
build::Build,
|
||||
deployment::{Deployment, DeploymentState},
|
||||
docker::{
|
||||
container::ContainerListItem, image::ImageListItem,
|
||||
network::NetworkListItem, volume::VolumeListItem,
|
||||
},
|
||||
deployment::Deployment,
|
||||
komodo_timestamp, optional_string,
|
||||
repo::Repo,
|
||||
server::{PeripheryInformation, Server, ServerHealth, ServerState},
|
||||
stack::{ComposeProject, Stack, StackService, StackState},
|
||||
stats::{SystemInformation, SystemStats},
|
||||
server::{Server, ServerState},
|
||||
stack::Stack,
|
||||
stats::SystemStats,
|
||||
};
|
||||
use periphery_client::api::{
|
||||
self, PollStatusResponse, git::GetLatestCommit,
|
||||
self, git::GetLatestCommit, poll::PollStatusResponse,
|
||||
};
|
||||
use serror::Serror;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -29,8 +25,8 @@ use crate::{
|
||||
helpers::periphery_client,
|
||||
monitor::{alert::check_alerts, record::record_server_stats},
|
||||
state::{
|
||||
db_client, deployment_status_cache, periphery_connections,
|
||||
repo_status_cache,
|
||||
CachedRepoStatus, db_client, deployment_status_cache,
|
||||
periphery_connections, repo_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,62 +39,24 @@ mod alert;
|
||||
mod helpers;
|
||||
mod record;
|
||||
mod resources;
|
||||
mod swarm;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct History<Curr: Default, Prev> {
|
||||
pub curr: Curr,
|
||||
pub prev: Option<Prev>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedServerStatus {
|
||||
pub id: String,
|
||||
pub state: ServerState,
|
||||
pub health: Option<ServerHealth>,
|
||||
pub periphery_info: Option<PeripheryInformation>,
|
||||
pub system_info: Option<SystemInformation>,
|
||||
pub system_stats: Option<SystemStats>,
|
||||
pub containers: Option<Vec<ContainerListItem>>,
|
||||
pub networks: Option<Vec<NetworkListItem>>,
|
||||
pub images: Option<Vec<ImageListItem>>,
|
||||
pub volumes: Option<Vec<VolumeListItem>>,
|
||||
pub projects: Option<Vec<ComposeProject>>,
|
||||
/// Store the error in reaching periphery
|
||||
pub err: Option<serror::Serror>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedDeploymentStatus {
|
||||
/// The deployment id
|
||||
pub id: String,
|
||||
pub state: DeploymentState,
|
||||
pub container: Option<ContainerListItem>,
|
||||
pub update_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedRepoStatus {
|
||||
pub latest_hash: Option<String>,
|
||||
pub latest_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedStackStatus {
|
||||
/// The stack id
|
||||
pub id: String,
|
||||
/// The stack state
|
||||
pub state: StackState,
|
||||
/// The services connected to the stack
|
||||
pub services: Vec<StackService>,
|
||||
}
|
||||
pub use swarm::update_cache_for_swarm;
|
||||
|
||||
const ADDITIONAL_MS: u128 = 500;
|
||||
|
||||
pub fn spawn_monitor_loop() {
|
||||
let interval: async_timing_util::Timelength = core_config()
|
||||
pub fn spawn_monitoring_loops() {
|
||||
let interval = core_config()
|
||||
.monitoring_interval
|
||||
.try_into()
|
||||
.expect("Invalid monitoring interval");
|
||||
spawn_server_monitoring_loop(interval);
|
||||
swarm::spawn_swarm_monitoring_loop(interval);
|
||||
}
|
||||
|
||||
fn spawn_server_monitoring_loop(
|
||||
interval: async_timing_util::Timelength,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
refresh_server_cache(komodo_timestamp()).await;
|
||||
loop {
|
||||
@@ -115,7 +73,7 @@ async fn refresh_server_cache(ts: i64) {
|
||||
Ok(servers) => servers,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"failed to get server list (manage status cache) | {e:#}"
|
||||
"Failed to get server list (refresh server cache) | {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -172,7 +130,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(None, None, None, None, None),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
@@ -190,7 +148,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(None, None, None, None, None),
|
||||
None,
|
||||
Serror::from(&e),
|
||||
)
|
||||
.await;
|
||||
@@ -202,14 +160,11 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
periphery_info,
|
||||
system_info,
|
||||
system_stats,
|
||||
mut containers,
|
||||
networks,
|
||||
images,
|
||||
volumes,
|
||||
projects,
|
||||
mut docker,
|
||||
} = match periphery
|
||||
.request(api::PollStatus {
|
||||
.request(api::poll::PollStatus {
|
||||
include_stats: server.config.stats_monitoring,
|
||||
include_docker: true,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -222,7 +177,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(None, None, None, None, None),
|
||||
None,
|
||||
Serror::from(&e),
|
||||
)
|
||||
.await;
|
||||
@@ -230,37 +185,44 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
}
|
||||
};
|
||||
|
||||
containers.iter_mut().for_each(|container| {
|
||||
container.server_id = Some(server.id.clone())
|
||||
});
|
||||
if let Some(docker) = &mut docker {
|
||||
docker.containers.iter_mut().for_each(|container| {
|
||||
container.server_id = Some(server.id.clone())
|
||||
});
|
||||
}
|
||||
|
||||
let containers = docker
|
||||
.as_ref()
|
||||
.map(|docker| docker.containers.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
let images = docker
|
||||
.as_ref()
|
||||
.map(|docker| docker.images.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
tokio::join!(
|
||||
resources::update_deployment_cache(
|
||||
server.name.clone(),
|
||||
resources.deployments,
|
||||
&containers,
|
||||
&images,
|
||||
containers,
|
||||
images,
|
||||
&resources.builds,
|
||||
),
|
||||
resources::update_stack_cache(
|
||||
server.name.clone(),
|
||||
resources.stacks,
|
||||
&containers,
|
||||
&images
|
||||
containers,
|
||||
images
|
||||
),
|
||||
);
|
||||
|
||||
insert_server_status(
|
||||
server,
|
||||
ServerState::Ok,
|
||||
Some(periphery_info),
|
||||
Some(system_info),
|
||||
system_stats.map(|stats| filter_volumes(server, stats)),
|
||||
(
|
||||
Some(containers.clone()),
|
||||
Some(networks),
|
||||
Some(images),
|
||||
Some(volumes),
|
||||
Some(projects),
|
||||
),
|
||||
docker,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
+188
-189
@@ -30,200 +30,12 @@ use crate::{
|
||||
services::extract_services_from_stack,
|
||||
},
|
||||
state::{
|
||||
CachedDeploymentStatus, CachedStackStatus, History,
|
||||
action_states, db_client, deployment_status_cache,
|
||||
stack_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{CachedDeploymentStatus, CachedStackStatus, History};
|
||||
|
||||
fn deployment_alert_sent_cache() -> &'static Mutex<HashSet<String>> {
|
||||
static CACHE: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn update_deployment_cache(
|
||||
server_name: String,
|
||||
deployments: Vec<Deployment>,
|
||||
containers: &[ContainerListItem],
|
||||
images: &[ImageListItem],
|
||||
builds: &[Build],
|
||||
) {
|
||||
let deployment_status_cache = deployment_status_cache();
|
||||
for deployment in deployments {
|
||||
let container = containers
|
||||
.iter()
|
||||
.find(|container| container.name == deployment.name)
|
||||
.cloned();
|
||||
let prev = deployment_status_cache
|
||||
.get(&deployment.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let state = container
|
||||
.as_ref()
|
||||
.map(|c| c.state.into())
|
||||
.unwrap_or(DeploymentState::NotDeployed);
|
||||
let image = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let (build_name, build_version) = builds
|
||||
.iter()
|
||||
.find(|build| build.id == build_id)
|
||||
.map(|b| (b.name.as_ref(), b.config.version))
|
||||
.unwrap_or(("Unknown", Default::default()));
|
||||
let version = if version.is_none() {
|
||||
build_version.to_string()
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
format!("{build_name}:{version}")
|
||||
}
|
||||
DeploymentImage::Image { image } => {
|
||||
// If image already has tag, leave it,
|
||||
// otherwise default the tag to latest
|
||||
if image.contains(':') {
|
||||
image.to_string()
|
||||
} else {
|
||||
format!("{image}:latest")
|
||||
}
|
||||
}
|
||||
};
|
||||
let update_available = if let Some(ContainerListItem {
|
||||
image_id: Some(curr_image_id),
|
||||
..
|
||||
}) = &container
|
||||
{
|
||||
// Docker will automatically strip `docker.io` from incoming image names re #468.
|
||||
// Need to strip it in order to match by image name and find available updates.
|
||||
let image = image.strip_prefix("docker.io/").unwrap_or(&image);
|
||||
images
|
||||
.iter()
|
||||
.find(|i| i.name == image)
|
||||
.map(|i| &i.id != curr_image_id)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if update_available {
|
||||
if deployment.config.auto_update {
|
||||
if state == DeploymentState::Running
|
||||
&& !action_states()
|
||||
.deployment
|
||||
.get_or_insert_default(&deployment.id)
|
||||
.await
|
||||
.busy()
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let id = deployment.id.clone();
|
||||
let server_name = server_name.clone();
|
||||
tokio::spawn(async move {
|
||||
match execute::inner_handler(
|
||||
ExecuteRequest::Deploy(Deploy {
|
||||
deployment: deployment.name.clone(),
|
||||
stop_time: None,
|
||||
stop_signal: None,
|
||||
}),
|
||||
auto_redeploy_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let ts = komodo_timestamp();
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: true,
|
||||
resolved_ts: ts.into(),
|
||||
level: SeverityLevel::Ok,
|
||||
target: ResourceTarget::Deployment(id.clone()),
|
||||
data: AlertData::DeploymentAutoUpdated {
|
||||
id,
|
||||
name: deployment.name,
|
||||
server_name,
|
||||
server_id: deployment.config.server_id,
|
||||
image,
|
||||
},
|
||||
};
|
||||
let res = db_client().alerts.insert_one(&alert).await;
|
||||
if let Err(e) = res {
|
||||
error!(
|
||||
"Failed to record DeploymentAutoUpdated to db | {e:#}"
|
||||
);
|
||||
}
|
||||
send_alerts(&[alert]).await;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to auto update Deployment {} | {e:#}",
|
||||
deployment.name
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if state == DeploymentState::Running
|
||||
&& deployment.config.send_alerts
|
||||
&& !deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.contains(&deployment.id)
|
||||
{
|
||||
// Add that it is already sent to the cache, so another alert won't be sent.
|
||||
deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(deployment.id.clone());
|
||||
let ts = komodo_timestamp();
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: true,
|
||||
resolved_ts: ts.into(),
|
||||
level: SeverityLevel::Ok,
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
data: AlertData::DeploymentImageUpdateAvailable {
|
||||
id: deployment.id.clone(),
|
||||
name: deployment.name,
|
||||
server_name: server_name.clone(),
|
||||
server_id: deployment.config.server_id,
|
||||
image,
|
||||
},
|
||||
};
|
||||
let res = db_client().alerts.insert_one(&alert).await;
|
||||
if let Err(e) = res {
|
||||
error!(
|
||||
"Failed to record DeploymentImageUpdateAvailable to db | {e:#}"
|
||||
);
|
||||
}
|
||||
send_alerts(&[alert]).await;
|
||||
}
|
||||
} else {
|
||||
// If it sees there is no longer update available, remove
|
||||
// from the sent cache, so on next `update_available = true`
|
||||
// the cache is empty and a fresh alert will be sent.
|
||||
deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&deployment.id);
|
||||
}
|
||||
deployment_status_cache
|
||||
.insert(
|
||||
deployment.id.clone(),
|
||||
History {
|
||||
curr: CachedDeploymentStatus {
|
||||
id: deployment.id,
|
||||
state,
|
||||
container,
|
||||
update_available,
|
||||
},
|
||||
prev,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// (StackId, Service)
|
||||
fn stack_alert_sent_cache()
|
||||
-> &'static Mutex<HashSet<(String, String)>> {
|
||||
@@ -425,3 +237,190 @@ pub async fn update_stack_cache(
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
fn deployment_alert_sent_cache() -> &'static Mutex<HashSet<String>> {
|
||||
static CACHE: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn update_deployment_cache(
|
||||
server_name: String,
|
||||
deployments: Vec<Deployment>,
|
||||
containers: &[ContainerListItem],
|
||||
images: &[ImageListItem],
|
||||
builds: &[Build],
|
||||
) {
|
||||
let deployment_status_cache = deployment_status_cache();
|
||||
for deployment in deployments {
|
||||
let container = containers
|
||||
.iter()
|
||||
.find(|container| container.name == deployment.name)
|
||||
.cloned();
|
||||
let prev = deployment_status_cache
|
||||
.get(&deployment.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let state = container
|
||||
.as_ref()
|
||||
.map(|c| c.state.into())
|
||||
.unwrap_or(DeploymentState::NotDeployed);
|
||||
let image = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let (build_name, build_version) = builds
|
||||
.iter()
|
||||
.find(|build| build.id == build_id)
|
||||
.map(|b| (b.name.as_ref(), b.config.version))
|
||||
.unwrap_or(("Unknown", Default::default()));
|
||||
let version = if version.is_none() {
|
||||
build_version.to_string()
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
format!("{build_name}:{version}")
|
||||
}
|
||||
DeploymentImage::Image { image } => {
|
||||
// If image already has tag, leave it,
|
||||
// otherwise default the tag to latest
|
||||
if image.contains(':') {
|
||||
image.to_string()
|
||||
} else {
|
||||
format!("{image}:latest")
|
||||
}
|
||||
}
|
||||
};
|
||||
let update_available = if let Some(ContainerListItem {
|
||||
image_id: Some(curr_image_id),
|
||||
..
|
||||
}) = &container
|
||||
{
|
||||
// Docker will automatically strip `docker.io` from incoming image names re #468.
|
||||
// Need to strip it in order to match by image name and find available updates.
|
||||
let image = image.strip_prefix("docker.io/").unwrap_or(&image);
|
||||
images
|
||||
.iter()
|
||||
.find(|i| i.name == image)
|
||||
.map(|i| &i.id != curr_image_id)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if update_available {
|
||||
if deployment.config.auto_update {
|
||||
if state == DeploymentState::Running
|
||||
&& !action_states()
|
||||
.deployment
|
||||
.get_or_insert_default(&deployment.id)
|
||||
.await
|
||||
.busy()
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let id = deployment.id.clone();
|
||||
let server_name = server_name.clone();
|
||||
tokio::spawn(async move {
|
||||
match execute::inner_handler(
|
||||
ExecuteRequest::Deploy(Deploy {
|
||||
deployment: deployment.name.clone(),
|
||||
stop_time: None,
|
||||
stop_signal: None,
|
||||
}),
|
||||
auto_redeploy_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let ts = komodo_timestamp();
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: true,
|
||||
resolved_ts: ts.into(),
|
||||
level: SeverityLevel::Ok,
|
||||
target: ResourceTarget::Deployment(id.clone()),
|
||||
data: AlertData::DeploymentAutoUpdated {
|
||||
id,
|
||||
name: deployment.name,
|
||||
server_name,
|
||||
server_id: deployment.config.server_id,
|
||||
image,
|
||||
},
|
||||
};
|
||||
let res = db_client().alerts.insert_one(&alert).await;
|
||||
if let Err(e) = res {
|
||||
error!(
|
||||
"Failed to record DeploymentAutoUpdated to db | {e:#}"
|
||||
);
|
||||
}
|
||||
send_alerts(&[alert]).await;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to auto update Deployment {} | {e:#}",
|
||||
deployment.name
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if state == DeploymentState::Running
|
||||
&& deployment.config.send_alerts
|
||||
&& !deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.contains(&deployment.id)
|
||||
{
|
||||
// Add that it is already sent to the cache, so another alert won't be sent.
|
||||
deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(deployment.id.clone());
|
||||
let ts = komodo_timestamp();
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: true,
|
||||
resolved_ts: ts.into(),
|
||||
level: SeverityLevel::Ok,
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
data: AlertData::DeploymentImageUpdateAvailable {
|
||||
id: deployment.id.clone(),
|
||||
name: deployment.name,
|
||||
server_name: server_name.clone(),
|
||||
server_id: deployment.config.server_id,
|
||||
image,
|
||||
},
|
||||
};
|
||||
let res = db_client().alerts.insert_one(&alert).await;
|
||||
if let Err(e) = res {
|
||||
error!(
|
||||
"Failed to record DeploymentImageUpdateAvailable to db | {e:#}"
|
||||
);
|
||||
}
|
||||
send_alerts(&[alert]).await;
|
||||
}
|
||||
} else {
|
||||
// If it sees there is no longer update available, remove
|
||||
// from the sent cache, so on next `update_available = true`
|
||||
// the cache is empty and a fresh alert will be sent.
|
||||
deployment_alert_sent_cache()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&deployment.id);
|
||||
}
|
||||
deployment_status_cache
|
||||
.insert(
|
||||
deployment.id.clone(),
|
||||
History {
|
||||
curr: CachedDeploymentStatus {
|
||||
id: deployment.id,
|
||||
state,
|
||||
container,
|
||||
update_available,
|
||||
},
|
||||
prev,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_timing_util::wait_until_timelength;
|
||||
use cache::CloneCache;
|
||||
use database::mungos::find::find_collect;
|
||||
use formatting::format_serror;
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::entities::{
|
||||
docker::node::NodeState,
|
||||
komodo_timestamp,
|
||||
swarm::{Swarm, SwarmState},
|
||||
};
|
||||
use periphery_client::api::swarm::{
|
||||
PollSwarmStatus, PollSwarmStatusResponse,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
helpers::swarm::swarm_request_custom_timeout,
|
||||
state::{CachedSwarmStatus, db_client, swarm_status_cache},
|
||||
};
|
||||
|
||||
const ADDITIONAL_MS: u128 = 1000;
|
||||
|
||||
pub fn spawn_swarm_monitoring_loop(
|
||||
interval: async_timing_util::Timelength,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
refresh_swarm_cache(komodo_timestamp()).await;
|
||||
loop {
|
||||
let ts = (wait_until_timelength(interval, ADDITIONAL_MS).await
|
||||
- ADDITIONAL_MS) as i64;
|
||||
refresh_swarm_cache(ts).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn refresh_swarm_cache(_ts: i64) {
|
||||
let swarms =
|
||||
match find_collect(&db_client().swarms, None, None).await {
|
||||
Ok(swarms) => swarms,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to get swarm list (refresh swarm cache) | {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let futures = swarms.into_iter().map(|swarm| async move {
|
||||
update_cache_for_swarm(&swarm, false).await;
|
||||
});
|
||||
join_all(futures).await;
|
||||
// tokio::join!(check_alerts(ts), record_swarm_stats(ts));
|
||||
}
|
||||
|
||||
/// Makes sure cache for swarm doesn't update too frequently / simultaneously.
|
||||
/// If forced, will still block against simultaneous update.
|
||||
fn update_cache_for_swarm_controller()
|
||||
-> &'static CloneCache<String, Arc<Mutex<i64>>> {
|
||||
static CACHE: OnceLock<CloneCache<String, Arc<Mutex<i64>>>> =
|
||||
OnceLock::new();
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// The background loop will call this with force: false,
|
||||
/// which exits early if the lock is busy or it was completed too recently.
|
||||
/// If force is true, it will wait on simultaneous calls, and will
|
||||
/// ignore the restriction on being completed too recently.
|
||||
pub async fn update_cache_for_swarm(swarm: &Swarm, force: bool) {
|
||||
// Concurrency controller to ensure it isn't done too often
|
||||
// when it happens in other contexts.
|
||||
let controller = update_cache_for_swarm_controller()
|
||||
.get_or_insert_default(&swarm.id)
|
||||
.await;
|
||||
let mut lock = match controller.try_lock() {
|
||||
Ok(lock) => lock,
|
||||
Err(_) if force => controller.lock().await,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let now = komodo_timestamp();
|
||||
|
||||
// early return if called again sooner than 1s.
|
||||
if !force && *lock > now - 1_000 {
|
||||
return;
|
||||
}
|
||||
|
||||
*lock = now;
|
||||
|
||||
if swarm.config.server_ids.is_empty() {
|
||||
swarm_status_cache()
|
||||
.insert(
|
||||
swarm.id.clone(),
|
||||
CachedSwarmStatus {
|
||||
state: SwarmState::Unknown,
|
||||
inspect: None,
|
||||
lists: None,
|
||||
err: Some(format_serror(
|
||||
&anyhow!("No Servers configured as manager nodes").into(),
|
||||
)),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let PollSwarmStatusResponse { inspect, lists } =
|
||||
match swarm_request_custom_timeout(
|
||||
&swarm.config.server_ids,
|
||||
PollSwarmStatus {},
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(info) => info,
|
||||
Err(e) => {
|
||||
swarm_status_cache()
|
||||
.insert(
|
||||
swarm.id.clone(),
|
||||
CachedSwarmStatus {
|
||||
state: SwarmState::Unknown,
|
||||
inspect: None,
|
||||
lists: None,
|
||||
err: Some(format_serror(&e.into())),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = SwarmState::Healthy;
|
||||
|
||||
for node in &lists.nodes {
|
||||
if !matches!(node.state, Some(NodeState::READY)) {
|
||||
state = SwarmState::Unhealthy;
|
||||
}
|
||||
}
|
||||
|
||||
swarm_status_cache()
|
||||
.insert(
|
||||
swarm.id.clone(),
|
||||
CachedSwarmStatus {
|
||||
state,
|
||||
inspect,
|
||||
lists: Some(lists),
|
||||
err: None,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
connection::{
|
||||
PeripheryConnection, PeripheryConnectionArgs, ResponseChannels,
|
||||
TerminalChannels,
|
||||
},
|
||||
state::periphery_connections,
|
||||
};
|
||||
@@ -21,9 +20,9 @@ pub mod terminal;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PeripheryClient {
|
||||
/// Usually the server id
|
||||
pub id: String,
|
||||
pub responses: Arc<ResponseChannels>,
|
||||
pub terminals: Arc<TerminalChannels>,
|
||||
}
|
||||
|
||||
impl PeripheryClient {
|
||||
@@ -50,7 +49,6 @@ impl PeripheryClient {
|
||||
return Ok(PeripheryClient {
|
||||
id,
|
||||
responses: connection.responses.clone(),
|
||||
terminals: connection.terminals.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,7 +65,6 @@ impl PeripheryClient {
|
||||
Ok(PeripheryClient {
|
||||
id,
|
||||
responses: connection.responses.clone(),
|
||||
terminals: connection.terminals.clone(),
|
||||
})
|
||||
} else {
|
||||
// Core -> Periphery connection
|
||||
@@ -88,6 +85,20 @@ impl PeripheryClient {
|
||||
&self,
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
self
|
||||
.request_custom_timeout(request, Duration::from_secs(10))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn request_custom_timeout<T>(
|
||||
&self,
|
||||
request: T,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
@@ -116,24 +127,28 @@ impl PeripheryClient {
|
||||
.await
|
||||
.context("Failed to send request over channel")
|
||||
{
|
||||
// cleanup
|
||||
self.terminals.remove(&channel_id).await;
|
||||
self.responses.remove(&channel_id).await;
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Poll for the associated response
|
||||
loop {
|
||||
let message = response_receiever
|
||||
.recv()
|
||||
.with_timeout(Duration::from_secs(10))
|
||||
.await?;
|
||||
let res = async {
|
||||
// Poll for the associated response
|
||||
loop {
|
||||
let message =
|
||||
response_receiever.recv().with_timeout(timeout).await?;
|
||||
|
||||
// Still in progress, sent to avoid timeout.
|
||||
let Some(message) = message.decode()? else {
|
||||
continue;
|
||||
};
|
||||
// Still in progress, sent to avoid timeout.
|
||||
let Some(message) = message.decode()? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
return message.decode();
|
||||
return message.decode();
|
||||
}
|
||||
}
|
||||
.await;
|
||||
|
||||
self.responses.remove(&channel_id).await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ use std::{
|
||||
use anyhow::Context;
|
||||
use cache::CloneCache;
|
||||
use futures_util::Stream;
|
||||
use komodo_client::api::write::TerminalRecreateMode;
|
||||
use komodo_client::entities::terminal::{
|
||||
TerminalStdinMessageVariant, TerminalTarget,
|
||||
};
|
||||
use periphery_client::{
|
||||
api::terminal::{
|
||||
ConnectContainerAttach, ConnectContainerExec, ConnectTerminal,
|
||||
END_OF_OUTPUT, ExecuteContainerExec, ExecuteTerminal,
|
||||
},
|
||||
api::terminal::{ConnectTerminal, END_OF_OUTPUT, ExecuteTerminal},
|
||||
transport::EncodedTransportMessage,
|
||||
};
|
||||
use transport::channel::{Receiver, Sender};
|
||||
@@ -33,9 +32,10 @@ impl PeripheryClient {
|
||||
pub async fn connect_terminal(
|
||||
&self,
|
||||
terminal: String,
|
||||
target: TerminalTarget,
|
||||
) -> anyhow::Result<ConnectTerminalResponse> {
|
||||
tracing::trace!(
|
||||
"request | type: ConnectTerminal | terminal name: {terminal}",
|
||||
"request | type: ConnectTerminal | Terminal: {terminal} | Target: {target:?}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
@@ -44,7 +44,7 @@ impl PeripheryClient {
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ConnectTerminal { terminal })
|
||||
.request(ConnectTerminal { terminal, target })
|
||||
.await
|
||||
.context("Failed to create terminal connection")?;
|
||||
|
||||
@@ -53,94 +53,13 @@ impl PeripheryClient {
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Ok(Vec::with_capacity(17))) // 16 bytes uuid + 1 EncodedResponse
|
||||
.send_terminal(
|
||||
channel,
|
||||
Ok(vec![TerminalStdinMessageVariant::Begin.as_byte()]),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ConnectTerminalResponse {
|
||||
channel,
|
||||
sender: connection.sender.clone(),
|
||||
receiver,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument("ConnectContainerExec", skip(self), fields(server_id = self.id))]
|
||||
pub async fn connect_container_exec(
|
||||
&self,
|
||||
container: String,
|
||||
shell: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) -> anyhow::Result<ConnectTerminalResponse> {
|
||||
tracing::trace!(
|
||||
"request | type: ConnectContainerExec | container name: {container} | shell: {shell}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
periphery_connections().get(&self.id).await.with_context(
|
||||
|| format!("No connection found for server {}", self.id),
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ConnectContainerExec {
|
||||
container,
|
||||
shell,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create container exec connection")?;
|
||||
|
||||
let (sender, receiver) = transport::channel::channel();
|
||||
connection.terminals.insert(channel, sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Ok(Vec::with_capacity(17)))
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ConnectTerminalResponse {
|
||||
channel,
|
||||
sender: connection.sender.clone(),
|
||||
receiver,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument("ConnectContainerAttach", skip(self), fields(server_id = self.id))]
|
||||
pub async fn connect_container_attach(
|
||||
&self,
|
||||
container: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) -> anyhow::Result<ConnectTerminalResponse> {
|
||||
tracing::trace!(
|
||||
"request | type: ConnectContainerAttach | container name: {container}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
periphery_connections().get(&self.id).await.with_context(
|
||||
|| format!("No connection found for server {}", self.id),
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ConnectContainerAttach {
|
||||
container,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create container attach connection")?;
|
||||
|
||||
let (sender, receiver) = transport::channel::channel();
|
||||
connection.terminals.insert(channel, sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Ok(Vec::with_capacity(17)))
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
"Failed to send TerminalMessage Begin byte to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ConnectTerminalResponse {
|
||||
@@ -166,13 +85,14 @@ impl PeripheryClient {
|
||||
#[instrument("ExecuteTerminal", skip(self), fields(server_id = self.id))]
|
||||
pub async fn execute_terminal(
|
||||
&self,
|
||||
target: TerminalTarget,
|
||||
terminal: String,
|
||||
command: String,
|
||||
) -> anyhow::Result<
|
||||
impl Stream<Item = anyhow::Result<Vec<u8>>> + 'static,
|
||||
> {
|
||||
trace!(
|
||||
"sending request | type: ExecuteTerminal | terminal name: {terminal} | command: {command}",
|
||||
"sending request | type: ExecuteTerminal | {target:?} | terminal name: {terminal} | command: {command}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
@@ -181,65 +101,10 @@ impl PeripheryClient {
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ExecuteTerminal { terminal, command })
|
||||
.await
|
||||
.context("Failed to create execute terminal connection")?;
|
||||
|
||||
let (terminal_sender, terminal_receiver) =
|
||||
transport::channel::channel();
|
||||
connection.terminals.insert(channel, terminal_sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Ok(Vec::with_capacity(17)))
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ReceiverStream {
|
||||
channel,
|
||||
receiver: terminal_receiver,
|
||||
channels: connection.terminals.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Executes command on specified container,
|
||||
/// and streams the response ending in [KOMODO_EXIT_CODE][komodo_client::entities::KOMODO_EXIT_CODE]
|
||||
/// sentinal value as the expected final line of the stream.
|
||||
///
|
||||
/// Example final line:
|
||||
/// ```text
|
||||
/// __KOMODO_EXIT_CODE:0
|
||||
/// ```
|
||||
///
|
||||
/// This means the command exited with code 0 (success).
|
||||
///
|
||||
/// If this value is NOT the final item before stream closes, it means
|
||||
/// the container shell exited mid command, before giving status. Example: running `exit`.
|
||||
#[instrument("ExecuteContainerExec", skip(self), fields(server_id = self.id))]
|
||||
pub async fn execute_container_exec(
|
||||
&self,
|
||||
container: String,
|
||||
shell: String,
|
||||
command: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) -> anyhow::Result<ReceiverStream> {
|
||||
tracing::trace!(
|
||||
"sending request | type: ExecuteContainerExec | container: {container} | shell: {shell} | command: {command}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
periphery_connections().get(&self.id).await.with_context(
|
||||
|| format!("No connection found for server {}", self.id),
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ExecuteContainerExec {
|
||||
container,
|
||||
shell,
|
||||
.request(ExecuteTerminal {
|
||||
terminal,
|
||||
target,
|
||||
command,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create execute terminal connection")?;
|
||||
@@ -248,12 +113,16 @@ impl PeripheryClient {
|
||||
transport::channel::channel();
|
||||
connection.terminals.insert(channel, terminal_sender).await;
|
||||
|
||||
// Trigger forwarding to begin now that forwarding channel is ready.
|
||||
// This is required to not miss messages.
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Ok(Vec::with_capacity(17)))
|
||||
.await?;
|
||||
.send_terminal(
|
||||
channel,
|
||||
Ok(vec![TerminalStdinMessageVariant::Begin.as_byte()]),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ReceiverStream {
|
||||
channel,
|
||||
|
||||
+159
-38
@@ -1,10 +1,12 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mongo_indexed::doc;
|
||||
use database::mungos::find::find_collect;
|
||||
use database::{bson::Document, mongo_indexed::doc};
|
||||
use futures_util::{FutureExt, future::BoxFuture};
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::entities::ResourceTarget;
|
||||
use komodo_client::entities::permission::SpecificPermission;
|
||||
use komodo_client::{
|
||||
api::read::GetPermission,
|
||||
entities::{
|
||||
@@ -15,6 +17,7 @@ use komodo_client::{
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::resource::list_all_resources;
|
||||
use crate::{
|
||||
api::read::ReadArgs,
|
||||
config::core_config,
|
||||
@@ -162,68 +165,186 @@ pub fn get_user_permission_on_resource<'a, T: KomodoResource>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
|
||||
pub async fn get_resource_ids_for_user<T: KomodoResource>(
|
||||
pub async fn list_resources_for_user<T: KomodoResource>(
|
||||
filters: impl Into<Option<Document>>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Option<Vec<String>>> {
|
||||
// Check admin or transparent mode
|
||||
if user.admin || core_config().transparent_mode {
|
||||
return Ok(None);
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
// Check admin
|
||||
if user.admin {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
|
||||
let mut base = PermissionLevelAndSpecifics {
|
||||
level: if core_config().transparent_mode {
|
||||
PermissionLevel::Read
|
||||
} else {
|
||||
PermissionLevel::None
|
||||
},
|
||||
specific: Default::default(),
|
||||
};
|
||||
|
||||
// 'transparent_mode' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
|
||||
let resource_type = T::resource_type();
|
||||
|
||||
// Check user 'all' on variant
|
||||
if let Some(permission) = user.all.get(&resource_type).cloned()
|
||||
&& permission.level > PermissionLevel::None
|
||||
{
|
||||
return Ok(None);
|
||||
if let Some(all_permission) = user.all.get(&resource_type) {
|
||||
base.elevate(all_permission);
|
||||
// 'user.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Check user groups 'all' on variant
|
||||
let groups = get_user_user_groups(&user.id).await?;
|
||||
for group in &groups {
|
||||
if let Some(permission) = group.all.get(&resource_type).cloned()
|
||||
&& permission.level > PermissionLevel::None
|
||||
{
|
||||
return Ok(None);
|
||||
if let Some(all_permission) = group.all.get(&resource_type) {
|
||||
base.elevate(all_permission);
|
||||
// 'group.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (base, perms) = tokio::try_join!(
|
||||
// Get any resources with non-none base permission,
|
||||
find_collect(
|
||||
T::coll(),
|
||||
doc! { "$or": [
|
||||
{ "base_permission": { "$in": ["Read", "Execute", "Write"] } },
|
||||
{ "base_permission.level": { "$in": ["Read", "Execute", "Write"] } }
|
||||
] },
|
||||
None,
|
||||
)
|
||||
.map(|res| res.with_context(|| format!(
|
||||
"failed to query {resource_type} on db"
|
||||
))),
|
||||
let (all, permissions) = tokio::try_join!(
|
||||
list_all_resources::<T>(filters),
|
||||
// And any ids using the permissions table
|
||||
find_collect(
|
||||
&db_client().permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(&user.id, &groups)?,
|
||||
"resource_target.type": resource_type.as_ref(),
|
||||
"level": { "$in": ["Read", "Execute", "Write"] }
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map(|res| res.context("failed to query permissions on db"))
|
||||
)?;
|
||||
|
||||
// Add specific ids
|
||||
let ids = perms
|
||||
let permission_by_resource_id = permissions
|
||||
.into_iter()
|
||||
.map(|p| p.resource_target.extract_variant_id().1.to_string())
|
||||
// Chain in the ones with non-None base permissions
|
||||
.chain(base.into_iter().map(|res| res.id))
|
||||
// collect into hashset first to remove any duplicates
|
||||
.collect::<HashSet<_>>();
|
||||
.map(|perm| {
|
||||
(
|
||||
perm.resource_target.extract_variant_id().1.to_string(),
|
||||
perm,
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(Some(ids.into_iter().collect()))
|
||||
let mut resources = Vec::new();
|
||||
let mut additional_specific_cache =
|
||||
HashMap::<ResourceTarget, IndexSet<SpecificPermission>>::new();
|
||||
|
||||
for resource in all {
|
||||
let mut perm = if let Some(perm) =
|
||||
permission_by_resource_id.get(&resource.id)
|
||||
{
|
||||
base.join(perm)
|
||||
} else {
|
||||
base.clone()
|
||||
};
|
||||
// Check if already fulfils
|
||||
if perm.fulfills(&permission) {
|
||||
resources.push(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Also check if fulfills with inherited specific
|
||||
let additional_target = if let Some(additional_target) =
|
||||
T::inherit_specific_permissions_from(&resource)
|
||||
&& !additional_target.is_empty()
|
||||
{
|
||||
additional_target
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let additional_specific = match additional_specific_cache
|
||||
.get(&additional_target)
|
||||
.cloned()
|
||||
{
|
||||
Some(specific) => specific,
|
||||
None => {
|
||||
let specific = GetPermission {
|
||||
target: additional_target.clone(),
|
||||
}
|
||||
.resolve(&ReadArgs { user: user.clone() })
|
||||
.await
|
||||
.map_err(|e| e.error)
|
||||
.context(
|
||||
"failed to get user permission on additional target",
|
||||
)?
|
||||
.specific;
|
||||
additional_specific_cache
|
||||
.insert(additional_target, specific.clone());
|
||||
specific
|
||||
}
|
||||
};
|
||||
perm.specific.extend(additional_specific);
|
||||
if perm.fulfills(&permission) {
|
||||
resources.push(resource);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
|
||||
pub async fn list_resource_ids_for_user<T: KomodoResource>(
|
||||
filters: Option<Document>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Option<Vec<String>>> {
|
||||
// Check admin
|
||||
if user.admin {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut base = PermissionLevelAndSpecifics {
|
||||
level: if core_config().transparent_mode {
|
||||
PermissionLevel::Read
|
||||
} else {
|
||||
PermissionLevel::None
|
||||
},
|
||||
specific: Default::default(),
|
||||
};
|
||||
|
||||
// 'transparent_mode' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let resource_type = T::resource_type();
|
||||
|
||||
if let Some(all) = user.all.get(&resource_type) {
|
||||
base.elevate(all);
|
||||
// 'user.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Check user groups 'all' on variant
|
||||
let groups = get_user_user_groups(&user.id).await?;
|
||||
for group in &groups {
|
||||
if let Some(all) = group.all.get(&resource_type) {
|
||||
base.elevate(all);
|
||||
// 'group.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ids = list_resources_for_user::<T>(filters, user, permission)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| resource.id)
|
||||
.collect();
|
||||
|
||||
Ok(Some(ids))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ use komodo_client::{
|
||||
entities::{
|
||||
Operation, ResourceTarget, ResourceTargetVariant,
|
||||
komodo_timestamp,
|
||||
permission::{PermissionLevel, SpecificPermission},
|
||||
permission::{
|
||||
PermissionLevel, PermissionLevelAndSpecifics,
|
||||
SpecificPermission,
|
||||
},
|
||||
resource::{AddFilters, Resource, ResourceQuery},
|
||||
tag::Tag,
|
||||
to_general_name,
|
||||
@@ -43,7 +46,7 @@ use crate::{
|
||||
query::{get_tag, id_or_name_filter},
|
||||
update::{add_update, make_update},
|
||||
},
|
||||
permission::{get_check_permissions, get_resource_ids_for_user},
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
@@ -57,6 +60,7 @@ mod refresh;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod stack;
|
||||
mod swarm;
|
||||
mod sync;
|
||||
|
||||
pub use action::{
|
||||
@@ -251,30 +255,31 @@ pub async fn get<T: KomodoResource>(
|
||||
// LIST
|
||||
// ======
|
||||
|
||||
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
|
||||
pub async fn get_resource_object_ids_for_user<T: KomodoResource>(
|
||||
user: &User,
|
||||
) -> anyhow::Result<Option<Vec<ObjectId>>> {
|
||||
get_resource_ids_for_user::<T>(user).await.map(|ids| {
|
||||
ids.map(|ids| {
|
||||
ids
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect()
|
||||
})
|
||||
/// Get full resource list with no permissions check.
|
||||
pub async fn list_all_resources<T: KomodoResource>(
|
||||
filters: impl Into<Option<Document>>,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
find_collect(
|
||||
T::coll(),
|
||||
filters,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_for_user<T: KomodoResource>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
user: &User,
|
||||
// permissions: PermissionLevelAndSpecifics,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
) -> anyhow::Result<Vec<T::ListItem>> {
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
list_for_user_using_document::<T>(filters, user).await
|
||||
list_for_user_using_document::<T>(filters, user, permission).await
|
||||
}
|
||||
|
||||
// // pub async fn list_for_user_using_pattern<T: KomodoResource>(
|
||||
@@ -300,9 +305,9 @@ pub async fn list_for_user<T: KomodoResource>(
|
||||
pub async fn list_for_user_using_document<T: KomodoResource>(
|
||||
filters: Document,
|
||||
user: &User,
|
||||
// permissions: PermissionLevelAndSpecifics,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Vec<T::ListItem>> {
|
||||
let list = list_full_for_user_using_document::<T>(filters, user)
|
||||
let list = list_resources_for_user::<T>(filters, user, permission)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| T::to_list_item(resource));
|
||||
@@ -321,10 +326,12 @@ pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
|
||||
pattern: &str,
|
||||
query: ResourceQuery<T::QuerySpecifics>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
let resources =
|
||||
list_full_for_user::<T>(query, user, all_tags).await?;
|
||||
list_full_for_user::<T>(query, user, permission, all_tags)
|
||||
.await?;
|
||||
|
||||
let patterns = parse_string_list(pattern);
|
||||
let mut names = HashSet::<String>::new();
|
||||
@@ -360,32 +367,13 @@ pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
|
||||
pub async fn list_full_for_user<T: KomodoResource>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
user: &User,
|
||||
permissions: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
list_full_for_user_using_document::<T>(filters, user).await
|
||||
}
|
||||
|
||||
pub async fn list_full_for_user_using_document<T: KomodoResource>(
|
||||
mut filters: Document,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
if let Some(ids) =
|
||||
get_resource_object_ids_for_user::<T>(user).await?
|
||||
{
|
||||
filters.insert("_id", doc! { "$in": ids });
|
||||
}
|
||||
find_collect(
|
||||
T::coll(),
|
||||
filters,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
list_resources_for_user::<T>(filters, user, permissions).await
|
||||
}
|
||||
|
||||
pub type IdResourceMap<T> = HashMap<
|
||||
@@ -477,11 +465,13 @@ pub async fn create<T: KomodoResource>(
|
||||
|
||||
// Ensure an existing resource with same name doesn't already exist
|
||||
// The database indexing also ensures this but doesn't give a good error message.
|
||||
if list_full_for_user::<T>(Default::default(), system_user(), &[])
|
||||
if T::coll()
|
||||
.find_one(doc! { "name": &name })
|
||||
.await
|
||||
.context("Failed to list all resources for duplicate name check")?
|
||||
.into_iter()
|
||||
.any(|r| r.name == name)
|
||||
.context(
|
||||
"Failed to check existing resources for duplicate name check",
|
||||
)?
|
||||
.is_some()
|
||||
{
|
||||
return Err(
|
||||
anyhow!("Resource with name '{}' already exists", name)
|
||||
@@ -658,20 +648,21 @@ pub async fn update<T: KomodoResource>(
|
||||
fn resource_target<T: KomodoResource>(id: String) -> ResourceTarget {
|
||||
match T::resource_type() {
|
||||
ResourceTargetVariant::System => ResourceTarget::System(id),
|
||||
ResourceTargetVariant::Build => ResourceTarget::Build(id),
|
||||
ResourceTargetVariant::Builder => ResourceTarget::Builder(id),
|
||||
ResourceTargetVariant::Swarm => ResourceTarget::Swarm(id),
|
||||
ResourceTargetVariant::Server => ResourceTarget::Server(id),
|
||||
ResourceTargetVariant::Stack => ResourceTarget::Stack(id),
|
||||
ResourceTargetVariant::Deployment => {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
ResourceTargetVariant::Server => ResourceTarget::Server(id),
|
||||
ResourceTargetVariant::Build => ResourceTarget::Build(id),
|
||||
ResourceTargetVariant::Repo => ResourceTarget::Repo(id),
|
||||
ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id),
|
||||
ResourceTargetVariant::Procedure => ResourceTarget::Procedure(id),
|
||||
ResourceTargetVariant::Action => ResourceTarget::Action(id),
|
||||
ResourceTargetVariant::ResourceSync => {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
}
|
||||
ResourceTargetVariant::Stack => ResourceTarget::Stack(id),
|
||||
ResourceTargetVariant::Action => ResourceTarget::Action(id),
|
||||
ResourceTargetVariant::Builder => ResourceTarget::Builder(id),
|
||||
ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,16 +931,17 @@ where
|
||||
{
|
||||
let resource: ResourceTarget = resource.into();
|
||||
let (recent_field, id) = match resource {
|
||||
ResourceTarget::Swarm(id) => ("recents.Swarm", id),
|
||||
ResourceTarget::Server(id) => ("recents.Server", id),
|
||||
ResourceTarget::Stack(id) => ("recents.Stack", id),
|
||||
ResourceTarget::Deployment(id) => ("recents.Deployment", id),
|
||||
ResourceTarget::Build(id) => ("recents.Build", id),
|
||||
ResourceTarget::Repo(id) => ("recents.Repo", id),
|
||||
ResourceTarget::Procedure(id) => ("recents.Procedure", id),
|
||||
ResourceTarget::Action(id) => ("recents.Action", id),
|
||||
ResourceTarget::Stack(id) => ("recents.Stack", id),
|
||||
ResourceTarget::ResourceSync(id) => ("recents.ResourceSync", id),
|
||||
ResourceTarget::Builder(id) => ("recents.Builder", id),
|
||||
ResourceTarget::Alerter(id) => ("recents.Alerter", id),
|
||||
ResourceTarget::ResourceSync(id) => ("recents.ResourceSync", id),
|
||||
ResourceTarget::System(_) => return,
|
||||
};
|
||||
if let Err(e) = db_client()
|
||||
|
||||
@@ -147,8 +147,9 @@ impl super::KomodoResource for Stack {
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
if let Some(projects) = &status.projects {
|
||||
if let Some(project) = projects
|
||||
if let Some(docker) = &status.docker {
|
||||
if let Some(project) = docker
|
||||
.projects
|
||||
.iter()
|
||||
.find(|project| project.name == project_name)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
use anyhow::Context;
|
||||
use database::mungos::mongodb::Collection;
|
||||
use komodo_client::entities::{
|
||||
Operation, ResourceTarget, ResourceTargetVariant,
|
||||
permission::PermissionLevel,
|
||||
resource::Resource,
|
||||
server::Server,
|
||||
swarm::{
|
||||
PartialSwarmConfig, Swarm, SwarmConfig, SwarmConfigDiff,
|
||||
SwarmInfo, SwarmListItem, SwarmListItemInfo, SwarmQuerySpecifics,
|
||||
},
|
||||
update::Update,
|
||||
user::User,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
monitor::update_cache_for_swarm,
|
||||
state::{db_client, swarm_status_cache},
|
||||
};
|
||||
|
||||
use super::get_check_permissions;
|
||||
|
||||
impl super::KomodoResource for Swarm {
|
||||
type Config = SwarmConfig;
|
||||
type PartialConfig = PartialSwarmConfig;
|
||||
type ConfigDiff = SwarmConfigDiff;
|
||||
type Info = SwarmInfo;
|
||||
type ListItem = SwarmListItem;
|
||||
type QuerySpecifics = SwarmQuerySpecifics;
|
||||
|
||||
fn resource_type() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Swarm
|
||||
}
|
||||
|
||||
fn resource_target(id: impl Into<String>) -> ResourceTarget {
|
||||
ResourceTarget::Swarm(id.into())
|
||||
}
|
||||
|
||||
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
|
||||
{
|
||||
&db_client().swarms
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
swarm: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let (state, err) = swarm_status_cache()
|
||||
.get(&swarm.id)
|
||||
.await
|
||||
.map(|status| (status.state, status.err.clone()))
|
||||
.unwrap_or_default();
|
||||
SwarmListItem {
|
||||
name: swarm.name,
|
||||
id: swarm.id,
|
||||
template: swarm.template,
|
||||
tags: swarm.tags,
|
||||
resource_type: ResourceTargetVariant::Swarm,
|
||||
info: SwarmListItemInfo {
|
||||
server_ids: swarm.config.server_ids,
|
||||
state,
|
||||
err,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn busy(_id: &String) -> anyhow::Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// CREATE
|
||||
|
||||
fn create_operation() -> Operation {
|
||||
Operation::CreateSwarm
|
||||
}
|
||||
|
||||
fn user_can_create(user: &User) -> bool {
|
||||
user.admin || !core_config().disable_non_admin_create
|
||||
}
|
||||
|
||||
async fn validate_create_config(
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
validate_config(config, user).await
|
||||
}
|
||||
|
||||
async fn post_create(
|
||||
created: &Self,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
update_cache_for_swarm(created, true).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
fn update_operation() -> Operation {
|
||||
Operation::UpdateSwarm
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
validate_config(config, user).await
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
updated: &Self,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
update_cache_for_swarm(updated, true).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// RENAME
|
||||
|
||||
fn rename_operation() -> Operation {
|
||||
Operation::RenameSwarm
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
fn delete_operation() -> Operation {
|
||||
Operation::DeleteSwarm
|
||||
}
|
||||
|
||||
async fn pre_delete(
|
||||
_swarm: &Self,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_delete(
|
||||
swarm: &Self,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
swarm_status_cache().remove(&swarm.id).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument("ValidateSwarmConfig", skip_all)]
|
||||
async fn validate_config(
|
||||
config: &mut PartialSwarmConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(server_ids) = &mut config.server_ids {
|
||||
let mut res = Vec::with_capacity(server_ids.capacity());
|
||||
for server_id in server_ids.iter_mut() {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server_id,
|
||||
user,
|
||||
PermissionLevel::Read.attach(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Cannot attach Server {server_id} to this Swarm")
|
||||
})?;
|
||||
res.push(server.id);
|
||||
}
|
||||
*server_ids = res;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn spawn_swarm_state_refresh_loop() {
|
||||
// tokio::spawn(async move {
|
||||
// loop {
|
||||
// refresh_swarm_state_cache().await;
|
||||
// tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// pub async fn refresh_swarm_state_cache() {
|
||||
// let _ = async {
|
||||
// let swarms = find_collect(&db_client().swarms, None, None)
|
||||
// .await
|
||||
// .context("failed to get swarms from db")?;
|
||||
// let cache = swarm_state_cache();
|
||||
// for swarm in swarms {
|
||||
// let state = get_swarm_state_from_db(&swarm.id).await;
|
||||
// cache.insert(swarm.id, state).await;
|
||||
// }
|
||||
// anyhow::Ok(())
|
||||
// }
|
||||
// .await
|
||||
// .inspect_err(|e| {
|
||||
// warn!("failed to refresh swarm state cache | {e:#}")
|
||||
// });
|
||||
// }
|
||||
+75
-7
@@ -4,9 +4,19 @@ use anyhow::Context;
|
||||
use arc_swap::ArcSwap;
|
||||
use cache::CloneCache;
|
||||
use komodo_client::entities::{
|
||||
action::ActionState, build::BuildState,
|
||||
deployment::DeploymentState, procedure::ProcedureState,
|
||||
repo::RepoState, stack::StackState,
|
||||
action::ActionState,
|
||||
build::BuildState,
|
||||
deployment::DeploymentState,
|
||||
docker::{
|
||||
DockerLists, SwarmLists, container::ContainerListItem,
|
||||
swarm::SwarmInspectInfo,
|
||||
},
|
||||
procedure::ProcedureState,
|
||||
repo::RepoState,
|
||||
server::{PeripheryInformation, ServerHealth, ServerState},
|
||||
stack::{StackService, StackState},
|
||||
stats::{SystemInformation, SystemStats},
|
||||
swarm::SwarmState,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -16,10 +26,6 @@ use crate::{
|
||||
helpers::{
|
||||
action_state::ActionStates, all_resources::AllResourcesById,
|
||||
},
|
||||
monitor::{
|
||||
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
|
||||
CachedStackStatus, History,
|
||||
},
|
||||
};
|
||||
|
||||
static DB_CLIENT: OnceLock<database::Client> = OnceLock::new();
|
||||
@@ -64,6 +70,43 @@ pub fn action_states() -> &'static ActionStates {
|
||||
ACTION_STATES.get_or_init(ActionStates::default)
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct History<Curr: Default, Prev> {
|
||||
pub curr: Curr,
|
||||
pub prev: Option<Prev>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedSwarmStatus {
|
||||
pub state: SwarmState,
|
||||
pub inspect: Option<SwarmInspectInfo>,
|
||||
pub lists: Option<SwarmLists>,
|
||||
/// Store the error in communicating with Swarm
|
||||
pub err: Option<String>,
|
||||
}
|
||||
|
||||
pub type SwarmStatusCache =
|
||||
CloneCache<String, Arc<CachedSwarmStatus>>;
|
||||
|
||||
pub fn swarm_status_cache() -> &'static SwarmStatusCache {
|
||||
static SWARM_STATUS_CACHE: OnceLock<SwarmStatusCache> =
|
||||
OnceLock::new();
|
||||
SWARM_STATUS_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedServerStatus {
|
||||
pub id: String,
|
||||
pub state: ServerState,
|
||||
pub health: Option<ServerHealth>,
|
||||
pub periphery_info: Option<PeripheryInformation>,
|
||||
pub system_info: Option<SystemInformation>,
|
||||
pub system_stats: Option<SystemStats>,
|
||||
pub docker: Option<DockerLists>,
|
||||
/// Store the error in reaching periphery
|
||||
pub err: Option<serror::Serror>,
|
||||
}
|
||||
|
||||
pub type ServerStatusCache =
|
||||
CloneCache<String, Arc<CachedServerStatus>>;
|
||||
|
||||
@@ -73,6 +116,16 @@ pub fn server_status_cache() -> &'static ServerStatusCache {
|
||||
SERVER_STATUS_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedStackStatus {
|
||||
/// The stack id
|
||||
pub id: String,
|
||||
/// The stack state
|
||||
pub state: StackState,
|
||||
/// The services connected to the stack
|
||||
pub services: Vec<StackService>,
|
||||
}
|
||||
|
||||
pub type StackStatusCache =
|
||||
CloneCache<String, Arc<History<CachedStackStatus, StackState>>>;
|
||||
|
||||
@@ -82,6 +135,15 @@ pub fn stack_status_cache() -> &'static StackStatusCache {
|
||||
STACK_STATUS_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedDeploymentStatus {
|
||||
/// The deployment id
|
||||
pub id: String,
|
||||
pub state: DeploymentState,
|
||||
pub container: Option<ContainerListItem>,
|
||||
pub update_available: bool,
|
||||
}
|
||||
|
||||
/// Cache of ids to status
|
||||
pub type DeploymentStatusCache = CloneCache<
|
||||
String,
|
||||
@@ -103,6 +165,12 @@ pub fn build_state_cache() -> &'static BuildStateCache {
|
||||
BUILD_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedRepoStatus {
|
||||
pub latest_hash: Option<String>,
|
||||
pub latest_message: Option<String>,
|
||||
}
|
||||
|
||||
pub type RepoStatusCache = CloneCache<String, Arc<CachedRepoStatus>>;
|
||||
|
||||
pub fn repo_status_cache() -> &'static RepoStatusCache {
|
||||
|
||||
@@ -14,6 +14,7 @@ use komodo_client::{
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::Log,
|
||||
@@ -34,6 +35,17 @@ use super::{
|
||||
include_resource_by_tags,
|
||||
};
|
||||
|
||||
impl ResourceSyncTrait for Swarm {
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Swarm {}
|
||||
|
||||
impl ResourceSyncTrait for Server {
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
|
||||
@@ -15,6 +15,7 @@ use komodo_client::{
|
||||
resource::Resource,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
toml::ResourceToml,
|
||||
@@ -174,6 +175,42 @@ impl ToToml for ResourceSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToToml for Swarm {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
let mut res =
|
||||
Vec::with_capacity(resource.config.server_ids.capacity());
|
||||
for server_id in &resource.config.server_ids {
|
||||
res.push(
|
||||
all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or(String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
_resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
|
||||
config
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
#[allow(clippy::single_match)]
|
||||
match key.as_str() {
|
||||
"server_ids" => {
|
||||
return Ok((String::from("servers"), value));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok((key, value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToToml for Stack {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
@@ -323,23 +323,9 @@ pub async fn get_updates_for_execution(
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
ResourceTarget::System(_) => {}
|
||||
ResourceTarget::Build(id) => {
|
||||
ResourceTarget::Swarm(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.deployments
|
||||
.swarms
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
@@ -351,16 +337,30 @@ pub async fn get_updates_for_execution(
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
ResourceTarget::Stack(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.stacks
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
@@ -386,9 +386,16 @@ pub async fn get_updates_for_execution(
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.stacks
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
@@ -796,37 +803,13 @@ async fn expand_user_group_permissions(
|
||||
}
|
||||
let matcher = Matcher::new(id)?;
|
||||
match variant {
|
||||
ResourceTargetVariant::Build => {
|
||||
ResourceTargetVariant::Swarm => {
|
||||
let permissions = all_resources
|
||||
.builds
|
||||
.swarms
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Build(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Builder => {
|
||||
let permissions = all_resources
|
||||
.builders
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Builder(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Deployment => {
|
||||
let permissions = all_resources
|
||||
.deployments
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Deployment(resource.name.clone()),
|
||||
target: ResourceTarget::Swarm(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
@@ -844,6 +827,42 @@ async fn expand_user_group_permissions(
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Stack => {
|
||||
let permissions = all_resources
|
||||
.stacks
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Stack(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Deployment => {
|
||||
let permissions = all_resources
|
||||
.deployments
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Deployment(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Build => {
|
||||
let permissions = all_resources
|
||||
.builds
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Build(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Repo => {
|
||||
let permissions = all_resources
|
||||
.repos
|
||||
@@ -856,18 +875,6 @@ async fn expand_user_group_permissions(
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Alerter => {
|
||||
let permissions = all_resources
|
||||
.alerters
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Alerter(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Procedure => {
|
||||
let permissions = all_resources
|
||||
.procedures
|
||||
@@ -906,13 +913,25 @@ async fn expand_user_group_permissions(
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Stack => {
|
||||
ResourceTargetVariant::Builder => {
|
||||
let permissions = all_resources
|
||||
.stacks
|
||||
.builders
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Stack(resource.name.clone()),
|
||||
target: ResourceTarget::Builder(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Alerter => {
|
||||
let permissions = all_resources
|
||||
.alerters
|
||||
.values()
|
||||
.filter(|resource| matcher.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Alerter(resource.name.clone()),
|
||||
level: permission.level,
|
||||
specific: permission.specific.clone(),
|
||||
});
|
||||
@@ -1040,23 +1059,9 @@ pub async fn convert_user_groups(
|
||||
.filter(|permission| permission.level > PermissionLevel::None)
|
||||
.map(|mut permission| {
|
||||
match &mut permission.resource_target {
|
||||
ResourceTarget::Build(id) => {
|
||||
ResourceTarget::Swarm(id) => {
|
||||
*id = all
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all
|
||||
.deployments
|
||||
.swarms
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
@@ -1068,16 +1073,30 @@ pub async fn convert_user_groups(
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
ResourceTarget::Stack(id) => {
|
||||
*id = all
|
||||
.repos
|
||||
.stacks
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all
|
||||
.alerters
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
@@ -1103,9 +1122,16 @@ pub async fn convert_user_groups(
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Stack(id) => {
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all
|
||||
.stacks
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures_util::SinkExt;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
terminal::{
|
||||
ConnectContainerAttachQuery, ConnectContainerExecQuery,
|
||||
},
|
||||
write::TerminalRecreateMode,
|
||||
},
|
||||
entities::{permission::PermissionLevel, server::Server},
|
||||
};
|
||||
|
||||
use crate::permission::get_check_permissions;
|
||||
|
||||
#[instrument("ConnectContainerExec", skip(ws))]
|
||||
pub async fn exec(
|
||||
Query(ConnectContainerExecQuery {
|
||||
server,
|
||||
container,
|
||||
shell,
|
||||
recreate,
|
||||
}): Query<ConnectContainerExecQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((mut client_socket, user)) =
|
||||
super::user_ws_login(socket).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let server = match get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
super::handle_container_exec_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
container,
|
||||
shell,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument("ConnectContainerAttach", skip(ws))]
|
||||
pub async fn attach(
|
||||
Query(ConnectContainerAttachQuery {
|
||||
server,
|
||||
container,
|
||||
recreate,
|
||||
}): Query<ConnectContainerAttachQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((mut client_socket, user)) =
|
||||
super::user_ws_login(socket).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let server = match get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
super::handle_container_attach_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
container,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures_util::SinkExt;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
terminal::{
|
||||
ConnectDeploymentAttachQuery, ConnectDeploymentExecQuery,
|
||||
},
|
||||
write::TerminalRecreateMode,
|
||||
},
|
||||
entities::{
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{permission::get_check_permissions, resource::get};
|
||||
|
||||
#[instrument("ConnectDeploymentExec", skip(ws))]
|
||||
pub async fn exec(
|
||||
Query(ConnectDeploymentExecQuery {
|
||||
deployment,
|
||||
shell,
|
||||
recreate,
|
||||
}): Query<ConnectDeploymentExecQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((mut client_socket, user)) =
|
||||
super::user_ws_login(socket).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let deployment = match get_check_permissions::<Deployment>(
|
||||
&deployment,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(deployment) => deployment,
|
||||
Err(e) => {
|
||||
debug!("could not get deployment | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let server =
|
||||
match get::<Server>(&deployment.config.server_id).await {
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
super::handle_container_exec_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
deployment.name,
|
||||
shell,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument("ConnectDeploymentAttach", skip(ws))]
|
||||
pub async fn attach(
|
||||
Query(ConnectDeploymentAttachQuery {
|
||||
deployment,
|
||||
recreate,
|
||||
}): Query<ConnectDeploymentAttachQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((mut client_socket, user)) =
|
||||
super::user_ws_login(socket).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let deployment = match get_check_permissions::<Deployment>(
|
||||
&deployment,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(deployment) => deployment,
|
||||
Err(e) => {
|
||||
debug!("could not get deployment | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let server =
|
||||
match get::<Server>(&deployment.config.server_id).await {
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
super::handle_container_attach_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
deployment.name,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
+2
-238
@@ -1,8 +1,6 @@
|
||||
use crate::{
|
||||
auth::{auth_api_key_check_enabled, auth_jwt_check_enabled},
|
||||
helpers::query::get_user,
|
||||
periphery::{PeripheryClient, terminal::ConnectTerminalResponse},
|
||||
state::periphery_connections,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
@@ -10,19 +8,9 @@ use axum::{
|
||||
extract::ws::{self, WebSocket},
|
||||
routing::get,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use komodo_client::{
|
||||
api::write::TerminalRecreateMode,
|
||||
entities::{server::Server, user::User},
|
||||
ws::WsLoginMessage,
|
||||
};
|
||||
use periphery_client::api::terminal::DisconnectTerminal;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use futures_util::SinkExt;
|
||||
use komodo_client::{entities::user::User, ws::WsLoginMessage};
|
||||
|
||||
mod container;
|
||||
mod deployment;
|
||||
mod stack;
|
||||
mod terminal;
|
||||
mod update;
|
||||
|
||||
@@ -33,12 +21,6 @@ pub fn router() -> Router {
|
||||
// User facing
|
||||
.route("/update", get(update::handler))
|
||||
.route("/terminal", get(terminal::handler))
|
||||
.route("/container/terminal", get(container::exec))
|
||||
.route("/container/terminal/attach", get(container::attach))
|
||||
.route("/deployment/terminal", get(deployment::exec))
|
||||
.route("/deployment/terminal/attach", get(deployment::attach))
|
||||
.route("/stack/terminal", get(stack::exec))
|
||||
.route("/stack/terminal/attach", get(stack::attach))
|
||||
}
|
||||
|
||||
async fn user_ws_login(
|
||||
@@ -129,221 +111,3 @@ async fn check_user_valid(user_id: &str) -> anyhow::Result<User> {
|
||||
}
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
async fn handle_container_exec_terminal(
|
||||
mut client_socket: WebSocket,
|
||||
server: &Server,
|
||||
container: String,
|
||||
shell: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) {
|
||||
let periphery = match crate::helpers::periphery_client(server).await
|
||||
{
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
debug!("couldn't get periphery | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(ws::Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connecting to periphery container exec websocket");
|
||||
|
||||
let response = match periphery
|
||||
.connect_container_exec(container, shell, recreate)
|
||||
.await
|
||||
{
|
||||
Ok(ws) => ws,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed connect to periphery container exec websocket | {e:#}"
|
||||
);
|
||||
let _ = client_socket
|
||||
.send(ws::Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connected to periphery container exec websocket");
|
||||
|
||||
forward_ws_channel(periphery, client_socket, response).await
|
||||
}
|
||||
|
||||
async fn handle_container_attach_terminal(
|
||||
mut client_socket: WebSocket,
|
||||
server: &Server,
|
||||
container: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) {
|
||||
let periphery = match crate::helpers::periphery_client(server).await
|
||||
{
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
debug!("couldn't get periphery | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(ws::Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connecting to periphery container exec websocket");
|
||||
|
||||
let response = match periphery
|
||||
.connect_container_attach(container, recreate)
|
||||
.await
|
||||
{
|
||||
Ok(ws) => ws,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed connect to periphery container attach websocket | {e:#}"
|
||||
);
|
||||
let _ = client_socket
|
||||
.send(ws::Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connected to periphery container attach websocket");
|
||||
|
||||
forward_ws_channel(periphery, client_socket, response).await
|
||||
}
|
||||
|
||||
async fn forward_ws_channel(
|
||||
periphery: PeripheryClient,
|
||||
client_socket: axum::extract::ws::WebSocket,
|
||||
ConnectTerminalResponse {
|
||||
channel,
|
||||
sender: periphery_sender,
|
||||
receiver: mut periphery_receiver,
|
||||
}: ConnectTerminalResponse,
|
||||
) {
|
||||
let (mut client_send, mut client_receive) = client_socket.split();
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
periphery_receiver.set_cancel(cancel.clone());
|
||||
|
||||
trace!("starting ws exchange");
|
||||
|
||||
let core_to_periphery = async {
|
||||
loop {
|
||||
let client_recv_res = tokio::select! {
|
||||
res = client_receive.next() => res,
|
||||
_ = cancel.cancelled() => {
|
||||
trace!("core to periphery read: cancelled from inside");
|
||||
break;
|
||||
}
|
||||
};
|
||||
match client_recv_res {
|
||||
Some(Ok(ws::Message::Binary(bytes))) => {
|
||||
if let Err(e) = periphery_sender
|
||||
.send_terminal(channel, Ok(bytes.into()))
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send terminal message | {e:?}",);
|
||||
cancel.cancel();
|
||||
break;
|
||||
};
|
||||
}
|
||||
Some(Ok(ws::Message::Text(text))) => {
|
||||
let bytes: Bytes = text.into();
|
||||
if let Err(e) = periphery_sender
|
||||
.send_terminal(channel, Ok(bytes.into()))
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send terminal message | {e:?}",);
|
||||
cancel.cancel();
|
||||
break;
|
||||
};
|
||||
}
|
||||
Some(Ok(ws::Message::Close(_frame))) => {
|
||||
let _ = periphery_sender
|
||||
.send_terminal(
|
||||
channel,
|
||||
Err(anyhow!("Client disconnected")),
|
||||
)
|
||||
.await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
Some(Err(_e)) => {
|
||||
let _ = periphery_sender
|
||||
.send_terminal(
|
||||
channel,
|
||||
Err(anyhow!("Client disconnected")),
|
||||
)
|
||||
.await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
let _ = periphery_sender
|
||||
.send_terminal(
|
||||
channel,
|
||||
Err(anyhow!("Client disconnected")),
|
||||
)
|
||||
.await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
// Ignore
|
||||
Some(Ok(_)) => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let periphery_to_core = async {
|
||||
loop {
|
||||
// Already adheres to cancellation token
|
||||
match periphery_receiver.recv().await {
|
||||
Ok(Ok(bytes)) => {
|
||||
if let Err(e) =
|
||||
client_send.send(ws::Message::Binary(bytes.into())).await
|
||||
{
|
||||
debug!("{e:?}");
|
||||
cancel.cancel();
|
||||
break;
|
||||
};
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
let _ = client_send
|
||||
.send(ws::Message::Text(format!("{e:#}").into()))
|
||||
.await;
|
||||
let _ = client_send.close().await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ =
|
||||
client_send.send(ws::Message::text("STREAM EOF")).await;
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::join!(core_to_periphery, periphery_to_core);
|
||||
|
||||
// Cleanup
|
||||
if let Err(e) =
|
||||
periphery.request(DisconnectTerminal { channel }).await
|
||||
{
|
||||
warn!(
|
||||
"Failed to disconnect Periphery terminal forwarding | {e:#}",
|
||||
)
|
||||
}
|
||||
if let Some(connection) =
|
||||
periphery_connections().get(&periphery.id).await
|
||||
{
|
||||
connection.terminals.remove(&channel).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures_util::SinkExt;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
terminal::{ConnectStackAttachQuery, ConnectStackExecQuery},
|
||||
write::TerminalRecreateMode,
|
||||
},
|
||||
entities::{
|
||||
permission::PermissionLevel, server::Server, stack::Stack,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
permission::get_check_permissions, resource::get,
|
||||
state::stack_status_cache,
|
||||
};
|
||||
|
||||
#[instrument("ConnectStackExec", skip(ws))]
|
||||
pub async fn exec(
|
||||
Query(ConnectStackExecQuery {
|
||||
stack,
|
||||
service,
|
||||
shell,
|
||||
recreate,
|
||||
}): Query<ConnectStackExecQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((client_socket, server, container)) =
|
||||
login_get_server_container(socket, &stack, &service).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
super::handle_container_exec_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
container,
|
||||
shell,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument("ConnectStackAttach", skip(ws))]
|
||||
pub async fn attach(
|
||||
Query(ConnectStackAttachQuery {
|
||||
stack,
|
||||
service,
|
||||
recreate,
|
||||
}): Query<ConnectStackAttachQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(async move |socket| {
|
||||
let Some((client_socket, server, container)) =
|
||||
login_get_server_container(socket, &stack, &service).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
super::handle_container_attach_terminal(
|
||||
client_socket,
|
||||
&server,
|
||||
container,
|
||||
recreate.unwrap_or(TerminalRecreateMode::DifferentCommand),
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
async fn login_get_server_container(
|
||||
socket: axum::extract::ws::WebSocket,
|
||||
stack: &str,
|
||||
service: &str,
|
||||
) -> Option<(axum::extract::ws::WebSocket, Server, String)> {
|
||||
let (mut client_socket, user) =
|
||||
super::user_ws_login(socket).await?;
|
||||
|
||||
let stack = match get_check_permissions::<Stack>(
|
||||
stack,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(stack) => stack,
|
||||
Err(e) => {
|
||||
debug!("could not get stack | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let server = match get::<Server>(&stack.config.server_id).await {
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(status) = stack_status_cache().get(&stack.id).await else {
|
||||
debug!("could not get stack status");
|
||||
let _ = client_socket
|
||||
.send(Message::text(String::from(
|
||||
"ERROR: could not get stack status",
|
||||
)))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return None;
|
||||
};
|
||||
|
||||
let container = match status
|
||||
.curr
|
||||
.services
|
||||
.iter()
|
||||
.find(|s| s.service == service)
|
||||
.map(|s| s.container.as_ref())
|
||||
{
|
||||
Some(Some(container)) => container.name.clone(),
|
||||
Some(None) => {
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!(
|
||||
"ERROR: Service {service} container could not be found"
|
||||
)))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return None;
|
||||
}
|
||||
None => {
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!(
|
||||
"ERROR: Service {service} could not be found"
|
||||
)))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((client_socket, server, container))
|
||||
}
|
||||
+159
-54
@@ -1,23 +1,27 @@
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
extract::{FromRequestParts, WebSocketUpgrade, ws},
|
||||
http::request,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures_util::SinkExt;
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt as _};
|
||||
use komodo_client::{
|
||||
api::terminal::ConnectTerminalQuery,
|
||||
entities::{permission::PermissionLevel, server::Server},
|
||||
api::terminal::ConnectTerminalQuery, entities::user::User,
|
||||
};
|
||||
use periphery_client::api::terminal::DisconnectTerminal;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client, permission::get_check_permissions,
|
||||
ws::forward_ws_channel,
|
||||
helpers::terminal::setup_target_for_user,
|
||||
periphery::{PeripheryClient, terminal::ConnectTerminalResponse},
|
||||
state::periphery_connections,
|
||||
};
|
||||
|
||||
#[instrument("ConnectTerminal", skip(ws))]
|
||||
pub async fn handler(
|
||||
Query(ConnectTerminalQuery { server, terminal }): Query<
|
||||
ConnectTerminalQuery,
|
||||
>,
|
||||
Qs(query): Qs<ConnectTerminalQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| async move {
|
||||
@@ -27,52 +31,153 @@ pub async fn handler(
|
||||
return;
|
||||
};
|
||||
|
||||
let server = match get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
debug!("could not get server | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let periphery = match periphery_client(&server).await {
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
debug!("couldn't get periphery | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connecting to periphery terminal websocket");
|
||||
|
||||
let response = match periphery.connect_terminal(terminal).await {
|
||||
Ok(ws) => ws,
|
||||
Err(e) => {
|
||||
debug!("Failed connect to periphery terminal | {e:#}");
|
||||
let _ = client_socket
|
||||
.send(Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
trace!("connected to periphery terminal websocket");
|
||||
let (periphery, response) =
|
||||
match setup_forwarding(query, &user).await {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
let _ = client_socket
|
||||
.send(ws::Message::text(format!("ERROR: {e:#}")))
|
||||
.await;
|
||||
let _ = client_socket.close().await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
forward_ws_channel(periphery, client_socket, response).await
|
||||
})
|
||||
}
|
||||
|
||||
async fn setup_forwarding(
|
||||
ConnectTerminalQuery {
|
||||
target,
|
||||
terminal,
|
||||
init,
|
||||
}: ConnectTerminalQuery,
|
||||
user: &User,
|
||||
) -> anyhow::Result<(PeripheryClient, ConnectTerminalResponse)> {
|
||||
let (target, terminal, periphery) =
|
||||
setup_target_for_user(target, terminal, init, user).await?;
|
||||
let response = periphery.connect_terminal(terminal, target).await?;
|
||||
Ok((periphery, response))
|
||||
}
|
||||
|
||||
async fn forward_ws_channel(
|
||||
periphery: PeripheryClient,
|
||||
client_socket: axum::extract::ws::WebSocket,
|
||||
ConnectTerminalResponse {
|
||||
channel,
|
||||
sender: periphery_sender,
|
||||
receiver: mut periphery_receiver,
|
||||
}: ConnectTerminalResponse,
|
||||
) {
|
||||
let (mut client_send, mut client_receive) = client_socket.split();
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
periphery_receiver.set_cancel(cancel.clone());
|
||||
|
||||
trace!("starting ws exchange");
|
||||
|
||||
let core_to_periphery = async {
|
||||
loop {
|
||||
let client_recv_res = tokio::select! {
|
||||
res = client_receive.next() => res,
|
||||
_ = cancel.cancelled() => break,
|
||||
};
|
||||
let bytes = match client_recv_res {
|
||||
Some(Ok(ws::Message::Binary(bytes))) => bytes.into(),
|
||||
Some(Ok(ws::Message::Text(text))) => {
|
||||
let bytes: Bytes = text.into();
|
||||
bytes.into()
|
||||
}
|
||||
Some(Ok(ws::Message::Close(_frame))) => {
|
||||
break;
|
||||
}
|
||||
Some(Err(_e)) => {
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
// Ignore
|
||||
Some(Ok(_)) => continue,
|
||||
};
|
||||
if let Err(_e) =
|
||||
periphery_sender.send_terminal(channel, Ok(bytes)).await
|
||||
{
|
||||
break;
|
||||
};
|
||||
}
|
||||
cancel.cancel();
|
||||
let _ = periphery_sender
|
||||
.send_terminal(channel, Err(anyhow!("Client disconnected")))
|
||||
.await;
|
||||
};
|
||||
|
||||
let periphery_to_core = async {
|
||||
loop {
|
||||
// Already adheres to cancellation token
|
||||
match periphery_receiver.recv().await {
|
||||
Ok(Ok(bytes)) => {
|
||||
if let Err(e) =
|
||||
client_send.send(ws::Message::Binary(bytes.into())).await
|
||||
{
|
||||
debug!("{e:?}");
|
||||
break;
|
||||
};
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
let _ = client_send
|
||||
.send(ws::Message::text(format!("{e:#}")))
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ =
|
||||
client_send.send(ws::Message::text("STREAM EOF")).await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = client_send.close().await;
|
||||
cancel.cancel();
|
||||
};
|
||||
|
||||
tokio::join!(core_to_periphery, periphery_to_core);
|
||||
|
||||
// Cleanup
|
||||
if let Err(e) =
|
||||
periphery.request(DisconnectTerminal { channel }).await
|
||||
{
|
||||
warn!(
|
||||
"Failed to disconnect Periphery terminal forwarding | {e:#}",
|
||||
)
|
||||
}
|
||||
if let Some(connection) =
|
||||
periphery_connections().get(&periphery.id).await
|
||||
{
|
||||
connection.terminals.remove(&channel).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Qs<T>(pub T);
|
||||
|
||||
impl<S, T> FromRequestParts<S> for Qs<T>
|
||||
where
|
||||
S: Send + Sync,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
type Rejection = axum::response::Response;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut request::Parts,
|
||||
_state: &S,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
let raw = parts.uri.query().unwrap_or_default();
|
||||
serde_qs::from_str::<T>(raw).map(Qs).map_err(|e| {
|
||||
axum::response::IntoResponse::into_response((
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
format!("Failed to parse request query: {e}"),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ serror = { workspace = true, features = ["axum"] }
|
||||
async_timing_util.workspace = true
|
||||
derive_variants.workspace = true
|
||||
resolver_api.workspace = true
|
||||
run_command.workspace = true
|
||||
# external
|
||||
hickory-resolver.workspace = true
|
||||
serde_yaml_ng.workspace = true
|
||||
@@ -55,9 +54,10 @@ sysinfo.workspace = true
|
||||
dotenvy.workspace = true
|
||||
anyhow.workspace = true
|
||||
rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
bytes.workspace = true
|
||||
serde.workspace = true
|
||||
shlex.workspace = true
|
||||
tokio.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
envy.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
FROM rust:1.90.0-bullseye AS builder
|
||||
FROM rust:1.90.0-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
@@ -14,7 +14,7 @@ COPY ./bin/periphery ./bin/periphery
|
||||
RUN cargo build -p komodo_periphery --release && cargo strip
|
||||
|
||||
# Final Image
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/periphery/starship.toml /starship.toml
|
||||
COPY ./bin/periphery/debian-deps.sh .
|
||||
|
||||
@@ -10,7 +10,7 @@ ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
|
||||
FROM ${X86_64_BINARIES} AS x86_64
|
||||
FROM ${AARCH64_BINARIES} AS aarch64
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/periphery/starship.toml /starship.toml
|
||||
COPY ./bin/periphery/debian-deps.sh .
|
||||
|
||||
@@ -6,7 +6,7 @@ ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
|
||||
# This is required to work with COPY --from
|
||||
FROM ${BINARIES_IMAGE} AS binaries
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
COPY ./bin/periphery/starship.toml /starship.toml
|
||||
COPY ./bin/periphery/debian-deps.sh .
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::{
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_sanitization,
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
run_komodo_standard_command,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use interpolate::Interpolator;
|
||||
@@ -267,7 +268,7 @@ impl Resolve<super::Args> for build::Build {
|
||||
"Pre Build",
|
||||
pre_build_path.as_path(),
|
||||
&pre_build.command,
|
||||
true,
|
||||
KomodoCommandMode::Multiline,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -323,7 +324,7 @@ impl Resolve<super::Args> for build::Build {
|
||||
"Docker Build",
|
||||
build_path.as_ref(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -349,7 +350,10 @@ impl Resolve<super::Args> for PruneBuilders {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker builder prune -a -f");
|
||||
Ok(run_komodo_command("Prune Builders", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Builders", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +370,9 @@ impl Resolve<super::Args> for PruneBuildx {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker buildx prune -a -f");
|
||||
Ok(run_komodo_command("Prune Buildx", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Buildx", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use formatting::format_serror;
|
||||
use komodo_client::entities::{
|
||||
FileContents, RepoExecutionArgs,
|
||||
repo::Repo,
|
||||
stack::{Stack, StackRemoteFileContents},
|
||||
stack::{AdditionalEnvFile, Stack, StackRemoteFileContents},
|
||||
to_path_compatible_name,
|
||||
update::Log,
|
||||
};
|
||||
@@ -60,24 +60,22 @@ pub async fn maybe_login_registry(
|
||||
|
||||
pub fn env_file_args(
|
||||
env_file_path: Option<&str>,
|
||||
additional_env_files: &[String],
|
||||
additional_env_files: &[AdditionalEnvFile],
|
||||
) -> anyhow::Result<String> {
|
||||
let mut res = String::new();
|
||||
|
||||
for file in additional_env_files.iter().filter(|&path| {
|
||||
let Some(komodo_path) = env_file_path else {
|
||||
return true;
|
||||
};
|
||||
// Filter komodo env out of additional env file if its also in there.
|
||||
// It will be always be added last / have highest priority.
|
||||
path != komodo_path
|
||||
}) {
|
||||
write!(res, " --env-file {file}").with_context(|| {
|
||||
format!("Failed to write --env-file arg for {file}")
|
||||
// Add additional env files (except komodo's own, which comes last)
|
||||
for file in additional_env_files
|
||||
.iter()
|
||||
.filter(|f| env_file_path != Some(f.path.as_str()))
|
||||
{
|
||||
let path = &file.path;
|
||||
write!(res, " --env-file {path}").with_context(|| {
|
||||
format!("Failed to write --env-file arg for {path}")
|
||||
})?;
|
||||
}
|
||||
|
||||
// Add this last, so it is applied on top
|
||||
// Add komodo's env file last for highest priority
|
||||
if let Some(file) = env_file_path {
|
||||
write!(res, " --env-file {file}").with_context(|| {
|
||||
format!("Failed to write --env-file arg for {file}")
|
||||
@@ -99,7 +97,7 @@ pub async fn compose_down(
|
||||
} else {
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
let log = run_komodo_command(
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Down",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} down{service_args}"),
|
||||
|
||||
@@ -2,28 +2,32 @@ use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_sanitization,
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use git::write_commit_file;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::entities::{
|
||||
FileContents, RepoExecutionResponse, all_logs_success,
|
||||
stack::{
|
||||
ComposeFile, ComposeProject, ComposeService,
|
||||
ComposeServiceDeploy, StackRemoteFileContents, StackServiceNames,
|
||||
use komodo_client::{
|
||||
entities::{
|
||||
FileContents, RepoExecutionResponse, all_logs_success,
|
||||
stack::{
|
||||
ComposeFile, ComposeService, ComposeServiceDeploy,
|
||||
StackRemoteFileContents, StackServiceNames,
|
||||
},
|
||||
to_path_compatible_name,
|
||||
update::Log,
|
||||
},
|
||||
to_path_compatible_name,
|
||||
update::Log,
|
||||
parsers::parse_multiline_command,
|
||||
};
|
||||
use periphery_client::api::compose::*;
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shell_escape::unix::escape;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
docker::compose::docker_compose,
|
||||
helpers::{format_extra_args, format_log_grep},
|
||||
};
|
||||
|
||||
@@ -32,67 +36,6 @@ mod write;
|
||||
|
||||
use helpers::*;
|
||||
|
||||
fn docker_compose() -> &'static str {
|
||||
if periphery_config().legacy_compose_cli {
|
||||
"docker-compose"
|
||||
} else {
|
||||
"docker compose"
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_compose_projects()
|
||||
-> anyhow::Result<Vec<ComposeProject>> {
|
||||
let docker_compose = docker_compose();
|
||||
let res = run_komodo_command(
|
||||
"List Projects",
|
||||
None,
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(format!(
|
||||
"failed to list compose projects using {docker_compose} ls"
|
||||
)));
|
||||
}
|
||||
|
||||
let res =
|
||||
serde_json::from_str::<Vec<DockerComposeLsItem>>(&res.stdout)
|
||||
.with_context(|| res.stdout.clone())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to parse '{docker_compose} ls' response to json"
|
||||
)
|
||||
})?
|
||||
.into_iter()
|
||||
.filter(|item| !item.name.is_empty())
|
||||
.map(|item| ComposeProject {
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
compose_files: item
|
||||
.config_files
|
||||
.split(',')
|
||||
.map(str::to_string)
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DockerComposeLsItem {
|
||||
#[serde(default, alias = "Name")]
|
||||
pub name: String,
|
||||
#[serde(alias = "Status")]
|
||||
pub status: Option<String>,
|
||||
/// Comma seperated list of paths
|
||||
#[serde(default, alias = "ConfigFiles")]
|
||||
pub config_files: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for GetComposeLog {
|
||||
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
|
||||
let GetComposeLog {
|
||||
@@ -111,7 +54,10 @@ impl Resolve<super::Args> for GetComposeLog {
|
||||
"{docker_compose} -p {project} logs --tail {tail}{timestamps} {}",
|
||||
services.join(" ")
|
||||
);
|
||||
Ok(run_komodo_command("get stack log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Get Stack Log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +82,10 @@ impl Resolve<super::Args> for GetComposeLogSearch {
|
||||
"{docker_compose} -p {project} logs --tail 5000{timestamps} {} 2>&1 | {grep}",
|
||||
services.join(" ")
|
||||
);
|
||||
Ok(run_komodo_command("Get stack log grep", None, command).await)
|
||||
Ok(
|
||||
run_komodo_shell_command("Search Stack Log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +303,7 @@ impl Resolve<super::Args> for ComposePull {
|
||||
)?;
|
||||
|
||||
let file_paths = stack
|
||||
.all_file_paths()
|
||||
.all_tracked_file_paths()
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
(
|
||||
@@ -395,7 +344,7 @@ impl Resolve<super::Args> for ComposePull {
|
||||
let project_name = stack.project_name(false);
|
||||
|
||||
let span = info_span!("RunComposePull");
|
||||
let log = run_komodo_command(
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Pull",
|
||||
run_directory.as_ref(),
|
||||
format!(
|
||||
@@ -492,7 +441,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Pre Deploy",
|
||||
pre_deploy_path.as_path(),
|
||||
&stack.config.pre_deploy.command,
|
||||
true,
|
||||
KomodoCommandMode::Multiline,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -536,7 +485,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Compose Config",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Standard,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -603,7 +552,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Compose Build",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -625,7 +574,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} pull{service_args}",
|
||||
);
|
||||
let span = info_span!("RunComposePull");
|
||||
let log = run_komodo_command(
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Pull",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
@@ -651,16 +600,31 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
|
||||
// Run compose up
|
||||
let extra_args = format_extra_args(&stack.config.extra_args);
|
||||
let command = format!(
|
||||
let mut command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} up -d{extra_args}{service_args}",
|
||||
);
|
||||
|
||||
// Apply compose cmd wrapper if configured
|
||||
let compose_cmd_wrapper =
|
||||
parse_multiline_command(&stack.config.compose_cmd_wrapper);
|
||||
if !compose_cmd_wrapper.is_empty() {
|
||||
if !compose_cmd_wrapper.contains("[[COMPOSE_COMMAND]]") {
|
||||
res.logs.push(Log::error(
|
||||
"Compose Command Wrapper",
|
||||
"compose_cmd_wrapper is configured but does not contain [[COMPOSE_COMMAND]] placeholder. The placeholder is required to inject the compose command.".to_string(),
|
||||
));
|
||||
return Ok(res);
|
||||
}
|
||||
command =
|
||||
compose_cmd_wrapper.replace("[[COMPOSE_COMMAND]]", &command);
|
||||
}
|
||||
|
||||
let span = info_span!("RunComposeUp");
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Up",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -680,7 +644,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Post Deploy",
|
||||
post_deploy_path.as_path(),
|
||||
&stack.config.post_deploy.command,
|
||||
true,
|
||||
KomodoCommandMode::Multiline,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -710,7 +674,7 @@ impl Resolve<super::Args> for ComposeExecution {
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let ComposeExecution { project, command } = self;
|
||||
let docker_compose = docker_compose();
|
||||
let log = run_komodo_command(
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Command",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} {command}"),
|
||||
@@ -804,7 +768,7 @@ impl Resolve<super::Args> for ComposeRun {
|
||||
let project_name = stack.project_name(true);
|
||||
|
||||
if pull.unwrap_or_default() {
|
||||
let pull_log = run_komodo_command(
|
||||
let pull_log = run_komodo_standard_command(
|
||||
"Compose Pull",
|
||||
run_directory.as_ref(),
|
||||
format!(
|
||||
@@ -867,7 +831,7 @@ impl Resolve<super::Args> for ComposeRun {
|
||||
"Compose Run",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command;
|
||||
use command::{
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::entities::{
|
||||
docker::{
|
||||
@@ -53,7 +55,10 @@ impl Resolve<super::Args> for GetContainerLog {
|
||||
};
|
||||
let command =
|
||||
format!("docker logs {name} --tail {tail}{timestamps}");
|
||||
Ok(run_komodo_command("Get container log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Get container log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +83,12 @@ impl Resolve<super::Args> for GetContainerLogSearch {
|
||||
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
|
||||
);
|
||||
Ok(
|
||||
run_komodo_command("Get container log grep", None, command)
|
||||
.await,
|
||||
run_komodo_shell_command(
|
||||
"Get container log grep",
|
||||
None,
|
||||
command,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -141,7 +150,7 @@ impl Resolve<super::Args> for StartContainer {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
run_komodo_standard_command(
|
||||
"Docker Start",
|
||||
None,
|
||||
format!("docker start {}", self.name),
|
||||
@@ -165,7 +174,7 @@ impl Resolve<super::Args> for RestartContainer {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
run_komodo_standard_command(
|
||||
"Docker Restart",
|
||||
None,
|
||||
format!("docker restart {}", self.name),
|
||||
@@ -189,7 +198,7 @@ impl Resolve<super::Args> for PauseContainer {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
run_komodo_standard_command(
|
||||
"Docker Pause",
|
||||
None,
|
||||
format!("docker pause {}", self.name),
|
||||
@@ -211,7 +220,7 @@ impl Resolve<super::Args> for UnpauseContainer {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
run_komodo_standard_command(
|
||||
"Docker Unpause",
|
||||
None,
|
||||
format!("docker unpause {}", self.name),
|
||||
@@ -236,11 +245,13 @@ impl Resolve<super::Args> for StopContainer {
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let StopContainer { name, signal, time } = self;
|
||||
let command = stop_container_command(&name, signal, time);
|
||||
let log = run_komodo_command("Docker Stop", None, command).await;
|
||||
let log =
|
||||
run_komodo_standard_command("Docker Stop", None, command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(&name, None, time);
|
||||
let mut log =
|
||||
run_komodo_command("Docker Stop", None, command).await;
|
||||
run_komodo_standard_command("Docker Stop", None, command)
|
||||
.await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -273,16 +284,22 @@ impl Resolve<super::Args> for RemoveContainer {
|
||||
let stop_command = stop_container_command(&name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let log =
|
||||
run_komodo_command("Docker Stop and Remove", None, command)
|
||||
.await;
|
||||
let log = run_komodo_shell_command(
|
||||
"Docker Stop and Remove",
|
||||
None,
|
||||
command,
|
||||
)
|
||||
.await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command = stop_container_command(&name, None, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let mut log =
|
||||
run_komodo_command("Docker Stop and Remove", None, command)
|
||||
.await;
|
||||
let mut log = run_komodo_shell_command(
|
||||
"Docker Stop and Remove",
|
||||
None,
|
||||
command,
|
||||
)
|
||||
.await;
|
||||
log.stderr = format!(
|
||||
"Old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -317,7 +334,10 @@ impl Resolve<super::Args> for RenameContainer {
|
||||
new_name,
|
||||
} = self;
|
||||
let command = format!("docker rename {curr_name} {new_name}");
|
||||
Ok(run_komodo_command("Docker Rename", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Docker Rename", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +354,10 @@ impl Resolve<super::Args> for PruneContainers {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker container prune -f");
|
||||
Ok(run_komodo_command("Prune Containers", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Containers", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +394,8 @@ impl Resolve<super::Args> for StartAllContainers {
|
||||
}
|
||||
let command = format!("docker start {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -412,7 +436,8 @@ impl Resolve<super::Args> for RestartAllContainers {
|
||||
}
|
||||
let command = format!("docker restart {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -453,7 +478,8 @@ impl Resolve<super::Args> for PauseAllContainers {
|
||||
}
|
||||
let command = format!("docker pause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -494,7 +520,8 @@ impl Resolve<super::Args> for UnpauseAllContainers {
|
||||
}
|
||||
let command = format!("docker unpause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -534,8 +561,8 @@ impl Resolve<super::Args> for StopAllContainers {
|
||||
return None;
|
||||
}
|
||||
Some(async move {
|
||||
run_komodo_command(
|
||||
&format!("docker stop {name}"),
|
||||
run_komodo_standard_command(
|
||||
&format!("Docker stop {name}"),
|
||||
None,
|
||||
stop_container_command(name, None, None),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command_with_sanitization;
|
||||
use command::{
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
@@ -102,7 +104,7 @@ impl Resolve<super::Args> for Deploy {
|
||||
"Docker Run",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::OnceLock;
|
||||
|
||||
use anyhow::Context;
|
||||
use cache::TimeoutCache;
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use komodo_client::entities::{
|
||||
deployment::extract_registry_domain,
|
||||
docker::{
|
||||
@@ -98,7 +98,7 @@ impl Resolve<super::Args> for PullImage {
|
||||
)
|
||||
.await?;
|
||||
anyhow::Ok(
|
||||
run_komodo_command(
|
||||
run_komodo_standard_command(
|
||||
"Docker Pull",
|
||||
None,
|
||||
format!("docker pull {name}"),
|
||||
@@ -130,7 +130,10 @@ impl Resolve<super::Args> for DeleteImage {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = format!("docker image rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Image", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Delete Image", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +150,10 @@ impl Resolve<super::Args> for PruneImages {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker image prune -a -f");
|
||||
Ok(run_komodo_command("Prune Images", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Images", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +192,10 @@ impl Resolve<super::Args> for CreateNetwork {
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
Ok(run_komodo_command("Create Network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Create Network", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +213,10 @@ impl Resolve<super::Args> for DeleteNetwork {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = format!("docker network rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Delete Network", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +233,10 @@ impl Resolve<super::Args> for PruneNetworks {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker network prune -f");
|
||||
Ok(run_komodo_command("Prune Networks", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Networks", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +269,10 @@ impl Resolve<super::Args> for DeleteVolume {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = format!("docker volume rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Volume", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Delete Volume", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +289,9 @@ impl Resolve<super::Args> for PruneVolumes {
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker volume prune -a -f");
|
||||
Ok(run_komodo_command("Prune Volumes", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune Volumes", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use derive_variants::EnumVariants;
|
||||
use encoding::{EncodedJsonMessage, EncodedResponse};
|
||||
use futures_util::FutureExt;
|
||||
use komodo_client::entities::{
|
||||
config::{DockerRegistry, GitProvider},
|
||||
server::PeripheryInformation,
|
||||
stats::SystemProcess,
|
||||
update::Log,
|
||||
};
|
||||
use periphery_client::api::{
|
||||
build::*, compose::*, container::*, docker::*, git::*, keys::*,
|
||||
stats::*, terminal::*, *,
|
||||
poll::*, stats::*, swarm::*, terminal::*, *,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
api::compose::list_compose_projects,
|
||||
config::periphery_config,
|
||||
state::{
|
||||
docker_client, host_public_ip, periphery_keys, stats_client,
|
||||
},
|
||||
};
|
||||
use crate::{config::periphery_config, state::stats_client};
|
||||
|
||||
pub mod terminal;
|
||||
|
||||
@@ -33,6 +25,8 @@ mod deploy;
|
||||
mod docker;
|
||||
mod git;
|
||||
mod keys;
|
||||
mod poll;
|
||||
mod swarm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
@@ -142,17 +136,27 @@ pub enum PeripheryRequest {
|
||||
// All in one (Write)
|
||||
PruneSystem(PruneSystem),
|
||||
|
||||
// Swarm
|
||||
PollSwarmStatus(PollSwarmStatus),
|
||||
InspectSwarmNode(InspectSwarmNode),
|
||||
InspectSwarmConfig(InspectSwarmConfig),
|
||||
InspectSwarmSecret(InspectSwarmSecret),
|
||||
InspectSwarmStack(InspectSwarmStack),
|
||||
InspectSwarmTask(InspectSwarmTask),
|
||||
InspectSwarmService(InspectSwarmService),
|
||||
GetSwarmServiceLog(GetSwarmServiceLog),
|
||||
GetSwarmServiceLogSearch(GetSwarmServiceLogSearch),
|
||||
|
||||
// Terminal
|
||||
ListTerminals(ListTerminals),
|
||||
CreateTerminal(CreateTerminal),
|
||||
CreateServerTerminal(CreateServerTerminal),
|
||||
CreateContainerExecTerminal(CreateContainerExecTerminal),
|
||||
CreateContainerAttachTerminal(CreateContainerAttachTerminal),
|
||||
DeleteTerminal(DeleteTerminal),
|
||||
DeleteAllTerminals(DeleteAllTerminals),
|
||||
ConnectTerminal(ConnectTerminal),
|
||||
ConnectContainerExec(ConnectContainerExec),
|
||||
ConnectContainerAttach(ConnectContainerAttach),
|
||||
DisconnectTerminal(DisconnectTerminal),
|
||||
ExecuteTerminal(ExecuteTerminal),
|
||||
ExecuteContainerExec(ExecuteContainerExec),
|
||||
|
||||
// Keys
|
||||
RotatePrivateKey(RotatePrivateKey),
|
||||
@@ -185,78 +189,6 @@ impl Resolve<Args> for GetVersion {
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<Args> for PollStatus {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &Args,
|
||||
) -> anyhow::Result<PollStatusResponse> {
|
||||
// Docker lists
|
||||
let docker_lists = async {
|
||||
let client = docker_client().load();
|
||||
let Some(client) = client.iter().next() else {
|
||||
return Default::default();
|
||||
};
|
||||
let containers =
|
||||
client.list_containers().await.unwrap_or_default();
|
||||
// Todo: handle errors better
|
||||
(
|
||||
tokio::join!(
|
||||
client
|
||||
.list_networks(&containers)
|
||||
.map(Result::unwrap_or_default),
|
||||
client
|
||||
.list_images(&containers)
|
||||
.map(Result::unwrap_or_default),
|
||||
client
|
||||
.list_volumes(&containers)
|
||||
.map(Result::unwrap_or_default)
|
||||
),
|
||||
containers,
|
||||
)
|
||||
};
|
||||
|
||||
let (
|
||||
((networks, images, volumes), containers),
|
||||
projects,
|
||||
stats_client,
|
||||
) = tokio::join!(
|
||||
docker_lists,
|
||||
list_compose_projects().map(Result::unwrap_or_default),
|
||||
stats_client().read(),
|
||||
);
|
||||
|
||||
let system_stats = if self.include_stats {
|
||||
Some(stats_client.stats.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let config = periphery_config();
|
||||
|
||||
Ok(PollStatusResponse {
|
||||
periphery_info: PeripheryInformation {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
public_key: periphery_keys().load().public.to_string(),
|
||||
terminals_disabled: config.disable_terminals,
|
||||
container_terminals_disabled: config
|
||||
.disable_container_terminals,
|
||||
stats_polling_rate: config.stats_polling_rate,
|
||||
docker_connected: docker_client().load().is_some(),
|
||||
public_ip: host_public_ip().await.cloned(),
|
||||
},
|
||||
system_info: stats_client.info.clone(),
|
||||
system_stats,
|
||||
containers,
|
||||
networks,
|
||||
images,
|
||||
volumes,
|
||||
projects,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<Args> for GetSystemProcesses {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -311,6 +243,9 @@ impl Resolve<Args> for PruneSystem {
|
||||
)]
|
||||
async fn resolve(self, args: &Args) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker system prune -a -f --volumes");
|
||||
Ok(run_komodo_command("Prune System", None, command).await)
|
||||
Ok(
|
||||
run_komodo_standard_command("Prune System", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
use futures_util::FutureExt;
|
||||
use komodo_client::entities::{
|
||||
docker::DockerLists, server::PeripheryInformation,
|
||||
};
|
||||
use periphery_client::api::poll::{PollStatus, PollStatusResponse};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
docker::{DockerClient, compose::list_compose_projects},
|
||||
state::{
|
||||
docker_client, host_public_ip, periphery_keys, stats_client,
|
||||
},
|
||||
};
|
||||
|
||||
impl Resolve<super::Args> for PollStatus {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<PollStatusResponse> {
|
||||
let stats_client = stats_client().read().await;
|
||||
|
||||
let system_stats = if self.include_stats {
|
||||
Some(stats_client.stats.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let docker = if self.include_docker {
|
||||
let client = docker_client().load();
|
||||
if let Some(client) = client.iter().next() {
|
||||
Some(docker_lists(client).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(PollStatusResponse {
|
||||
periphery_info: periphery_information().await,
|
||||
system_info: stats_client.info.clone(),
|
||||
system_stats,
|
||||
docker,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn periphery_information() -> PeripheryInformation {
|
||||
let config = periphery_config();
|
||||
PeripheryInformation {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
public_key: periphery_keys().load().public.to_string(),
|
||||
terminals_disabled: config.disable_terminals,
|
||||
container_terminals_disabled: config.disable_container_terminals,
|
||||
stats_polling_rate: config.stats_polling_rate,
|
||||
docker_connected: docker_client().load().is_some(),
|
||||
public_ip: host_public_ip().await.cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn docker_lists(client: &DockerClient) -> DockerLists {
|
||||
let containers = client.list_containers().await.unwrap_or_default();
|
||||
let (networks, images, volumes, projects) = tokio::join!(
|
||||
client
|
||||
.list_networks(&containers)
|
||||
.map(Result::unwrap_or_default),
|
||||
client
|
||||
.list_images(&containers)
|
||||
.map(Result::unwrap_or_default),
|
||||
client
|
||||
.list_volumes(&containers)
|
||||
.map(Result::unwrap_or_default),
|
||||
list_compose_projects().map(Result::unwrap_or_default),
|
||||
);
|
||||
DockerLists {
|
||||
containers,
|
||||
networks,
|
||||
images,
|
||||
volumes,
|
||||
projects,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
use anyhow::Context as _;
|
||||
use command::{
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use komodo_client::entities::{
|
||||
docker::{
|
||||
SwarmLists, config::SwarmConfig, node::SwarmNode,
|
||||
secret::SwarmSecret, service::SwarmService,
|
||||
stack::SwarmStackLists, task::SwarmTask,
|
||||
},
|
||||
update::Log,
|
||||
};
|
||||
use periphery_client::api::swarm::*;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
docker::{
|
||||
config::{inspect_swarm_config, list_swarm_configs},
|
||||
stack::{inspect_swarm_stack, list_swarm_stacks},
|
||||
},
|
||||
helpers::format_log_grep,
|
||||
state::docker_client,
|
||||
};
|
||||
|
||||
impl Resolve<super::Args> for PollSwarmStatus {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<PollSwarmStatusResponse> {
|
||||
let client = docker_client().load();
|
||||
let client = client
|
||||
.iter()
|
||||
.next()
|
||||
.context("Could not connect to docker client")?;
|
||||
let (inspect, nodes, services, tasks, secrets, configs, stacks) = tokio::join!(
|
||||
client.inspect_swarm(),
|
||||
client.list_swarm_nodes(),
|
||||
client.list_swarm_services(),
|
||||
client.list_swarm_tasks(),
|
||||
client.list_swarm_secrets(),
|
||||
list_swarm_configs(),
|
||||
list_swarm_stacks(),
|
||||
);
|
||||
Ok(PollSwarmStatusResponse {
|
||||
inspect: inspect.ok(),
|
||||
lists: SwarmLists {
|
||||
nodes: nodes.unwrap_or_default(),
|
||||
services: services.unwrap_or_default(),
|
||||
tasks: tasks.unwrap_or_default(),
|
||||
secrets: secrets.unwrap_or_default(),
|
||||
configs: configs.unwrap_or_default(),
|
||||
stacks: stacks.unwrap_or_default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ======
|
||||
// Node
|
||||
// ======
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmNode {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<SwarmNode> {
|
||||
let client = docker_client().load();
|
||||
let client = client
|
||||
.iter()
|
||||
.next()
|
||||
.context("Could not connect to docker client")?;
|
||||
client.inspect_swarm_node(&self.node).await
|
||||
}
|
||||
}
|
||||
|
||||
// =========
|
||||
// Service
|
||||
// =========
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmService {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<SwarmService> {
|
||||
let client = docker_client().load();
|
||||
let client = client
|
||||
.iter()
|
||||
.next()
|
||||
.context("Could not connect to docker client")?;
|
||||
client.inspect_swarm_service(&self.service).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<super::Args> for GetSwarmServiceLog {
|
||||
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
|
||||
let GetSwarmServiceLog {
|
||||
service,
|
||||
tail,
|
||||
timestamps,
|
||||
no_task_ids,
|
||||
no_resolve,
|
||||
details,
|
||||
} = self;
|
||||
let timestamps =
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let no_task_ids =
|
||||
no_task_ids.then_some(" --no-task-ids").unwrap_or_default();
|
||||
let no_resolve =
|
||||
no_resolve.then_some(" --no-resolve").unwrap_or_default();
|
||||
let details = details.then_some(" --details").unwrap_or_default();
|
||||
let command = format!(
|
||||
"docker service logs --tail {tail}{timestamps}{no_task_ids}{no_resolve}{details} {service}",
|
||||
);
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Get Swarm Service Log",
|
||||
None,
|
||||
command,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<super::Args> for GetSwarmServiceLogSearch {
|
||||
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
|
||||
let GetSwarmServiceLogSearch {
|
||||
service,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
no_task_ids,
|
||||
no_resolve,
|
||||
details,
|
||||
} = self;
|
||||
let timestamps =
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let no_task_ids =
|
||||
no_task_ids.then_some(" --no-task-ids").unwrap_or_default();
|
||||
let no_resolve =
|
||||
no_resolve.then_some(" --no-resolve").unwrap_or_default();
|
||||
let details = details.then_some(" --details").unwrap_or_default();
|
||||
let grep = format_log_grep(&terms, combinator, invert);
|
||||
let command = format!(
|
||||
"docker service logs --tail 5000{timestamps}{no_task_ids}{no_resolve}{details} {service} 2>&1 | {grep}",
|
||||
);
|
||||
Ok(
|
||||
run_komodo_shell_command(
|
||||
"Search Swarm Service Log",
|
||||
None,
|
||||
command,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ======
|
||||
// Task
|
||||
// ======
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmTask {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<SwarmTask> {
|
||||
let client = docker_client().load();
|
||||
let client = client
|
||||
.iter()
|
||||
.next()
|
||||
.context("Could not connect to docker client")?;
|
||||
client.inspect_swarm_task(&self.task).await
|
||||
}
|
||||
}
|
||||
|
||||
// ========
|
||||
// Secret
|
||||
// ========
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmSecret {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<SwarmSecret> {
|
||||
let client = docker_client().load();
|
||||
let client = client
|
||||
.iter()
|
||||
.next()
|
||||
.context("Could not connect to docker client")?;
|
||||
client.inspect_swarm_secret(&self.secret).await
|
||||
}
|
||||
}
|
||||
|
||||
// ========
|
||||
// Config
|
||||
// ========
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmConfig {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<Vec<SwarmConfig>> {
|
||||
inspect_swarm_config(&self.config).await
|
||||
}
|
||||
}
|
||||
|
||||
// =======
|
||||
// Stack
|
||||
// =======
|
||||
|
||||
impl Resolve<super::Args> for InspectSwarmStack {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<SwarmStackLists> {
|
||||
inspect_swarm_stack(self.stack).await
|
||||
}
|
||||
}
|
||||
+122
-244
@@ -1,11 +1,10 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use colored::Colorize;
|
||||
use futures_util::{Stream, StreamExt, TryStreamExt};
|
||||
use komodo_client::entities::{
|
||||
ContainerTerminalMode, KOMODO_EXIT_CODE, NoData,
|
||||
server::TerminalInfo,
|
||||
KOMODO_EXIT_CODE, NoData,
|
||||
terminal::{Terminal, TerminalStdinMessage, TerminalTarget},
|
||||
};
|
||||
use periphery_client::{
|
||||
api::terminal::*, transport::EncodedTransportMessage,
|
||||
@@ -30,17 +29,17 @@ impl Resolve<super::Args> for ListTerminals {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> anyhow::Result<Vec<TerminalInfo>> {
|
||||
) -> anyhow::Result<Vec<Terminal>> {
|
||||
clean_up_terminals().await;
|
||||
Ok(list_terminals(self.container.as_deref()).await)
|
||||
Ok(list_terminals(self.target.as_ref()).await)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for CreateTerminal {
|
||||
impl Resolve<super::Args> for CreateServerTerminal {
|
||||
#[instrument(
|
||||
"CreateTerminal",
|
||||
"CreateServerTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
@@ -56,12 +55,110 @@ impl Resolve<super::Args> for CreateTerminal {
|
||||
) -> anyhow::Result<NoData> {
|
||||
if periphery_config().disable_terminals {
|
||||
return Err(anyhow!(
|
||||
"Terminals are disabled in the periphery config"
|
||||
"Terminals are disabled in the Periphery config"
|
||||
));
|
||||
}
|
||||
create_terminal(self.name, self.command, self.recreate, None)
|
||||
.await
|
||||
.map(|_| NoData {})
|
||||
create_terminal(
|
||||
self.name,
|
||||
TerminalTarget::Server { server: None },
|
||||
self.command,
|
||||
self.recreate,
|
||||
)
|
||||
.await
|
||||
.map(|_| NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for CreateContainerExecTerminal {
|
||||
#[instrument(
|
||||
"CreateContainerExecTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
terminal = self.name,
|
||||
target = format!("{:?}", self.target),
|
||||
command = self.command,
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
args: &super::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
if periphery_config().disable_container_terminals {
|
||||
return Err(anyhow!(
|
||||
"Container Terminals are disabled in the Periphery config"
|
||||
));
|
||||
}
|
||||
let CreateContainerExecTerminal {
|
||||
name,
|
||||
target,
|
||||
container,
|
||||
command,
|
||||
recreate,
|
||||
} = self;
|
||||
let command = command.unwrap_or_else(|| String::from("sh"));
|
||||
if container.contains("&&") || command.contains("&&") {
|
||||
return Err(anyhow!(
|
||||
"The use of '&&' is forbidden in the container name or command"
|
||||
));
|
||||
}
|
||||
create_terminal(
|
||||
name,
|
||||
target,
|
||||
Some(format!("docker exec -it {container} {command}")),
|
||||
recreate,
|
||||
)
|
||||
.await
|
||||
.map(|_| NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for CreateContainerAttachTerminal {
|
||||
#[instrument(
|
||||
"CreateContainerAttachTerminal",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
terminal = self.name,
|
||||
target = format!("{:?}", self.target),
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
args: &super::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
if periphery_config().disable_container_terminals {
|
||||
return Err(anyhow!(
|
||||
"Container Terminals are disabled in the Periphery config"
|
||||
));
|
||||
}
|
||||
let CreateContainerAttachTerminal {
|
||||
name,
|
||||
target,
|
||||
container,
|
||||
recreate,
|
||||
} = self;
|
||||
if container.contains("&&") {
|
||||
return Err(anyhow!(
|
||||
"The use of '&&' is forbidden in the container name"
|
||||
));
|
||||
}
|
||||
create_terminal(
|
||||
name,
|
||||
target,
|
||||
Some(format!("docker attach {container} --sig-proxy=false")),
|
||||
recreate,
|
||||
)
|
||||
.await
|
||||
.map(|_| NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +178,7 @@ impl Resolve<super::Args> for DeleteTerminal {
|
||||
self,
|
||||
args: &super::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
delete_terminal(&self.terminal).await;
|
||||
delete_terminal(&self.target, &self.terminal).await;
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
@@ -119,12 +216,6 @@ impl Resolve<super::Args> for ConnectTerminal {
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
|
||||
if periphery_config().disable_terminals {
|
||||
return Err(anyhow!(
|
||||
"Terminals are disabled in the periphery config"
|
||||
));
|
||||
}
|
||||
|
||||
let connection =
|
||||
core_connections().get(&args.core).await.with_context(
|
||||
|| format!("Failed to find channel for {}", args.core),
|
||||
@@ -132,115 +223,7 @@ impl Resolve<super::Args> for ConnectTerminal {
|
||||
|
||||
clean_up_terminals().await;
|
||||
|
||||
let terminal = get_terminal(&self.terminal).await?;
|
||||
|
||||
let channel =
|
||||
spawn_terminal_forwarding(connection, terminal).await;
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for ConnectContainerExec {
|
||||
#[instrument(
|
||||
"ConnectContainerExec",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
container = self.container,
|
||||
shell = self.shell,
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
|
||||
if periphery_config().disable_container_terminals {
|
||||
return Err(anyhow!(
|
||||
"Container Terminals are disabled in the periphery config"
|
||||
));
|
||||
}
|
||||
|
||||
let connection =
|
||||
core_connections().get(&args.core).await.with_context(
|
||||
|| format!("Failed to find channel for {}", args.core),
|
||||
)?;
|
||||
|
||||
let ConnectContainerExec {
|
||||
container,
|
||||
shell,
|
||||
recreate,
|
||||
} = self;
|
||||
|
||||
if container.contains("&&") || shell.contains("&&") {
|
||||
return Err(anyhow!(
|
||||
"The use of '&&' is forbidden in the container name or shell"
|
||||
));
|
||||
}
|
||||
|
||||
// Create (recreate if shell changed)
|
||||
let terminal = create_terminal(
|
||||
container.clone(),
|
||||
Some(format!("docker exec -it {container} {shell}")),
|
||||
recreate,
|
||||
Some((container, ContainerTerminalMode::Exec)),
|
||||
)
|
||||
.await
|
||||
.context("Failed to create terminal for container exec")?;
|
||||
|
||||
let channel =
|
||||
spawn_terminal_forwarding(connection, terminal).await;
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for ConnectContainerAttach {
|
||||
#[instrument(
|
||||
"ConnectContainerAttach",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
container = self.container,
|
||||
recreate = format!("{:?}", self.recreate),
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
|
||||
if periphery_config().disable_container_terminals {
|
||||
return Err(anyhow!(
|
||||
"Container Terminals are disabled in the periphery config"
|
||||
));
|
||||
}
|
||||
|
||||
let connection =
|
||||
core_connections().get(&args.core).await.with_context(
|
||||
|| format!("Failed to find channel for {}", args.core),
|
||||
)?;
|
||||
|
||||
let ConnectContainerAttach {
|
||||
container,
|
||||
recreate,
|
||||
} = self;
|
||||
|
||||
if container.contains("&&") {
|
||||
return Err(anyhow!(
|
||||
"The use of '&&' is forbidden in the container name"
|
||||
));
|
||||
}
|
||||
|
||||
// Create (recreate if shell changed)
|
||||
let terminal = create_terminal(
|
||||
container.clone(),
|
||||
Some(format!("docker attach {container} --sig-proxy=false")),
|
||||
recreate,
|
||||
Some((container, ContainerTerminalMode::Attach)),
|
||||
)
|
||||
.await
|
||||
.context("Failed to create terminal for container attach")?;
|
||||
let terminal = get_terminal(&self.terminal, &self.target).await?;
|
||||
|
||||
let channel =
|
||||
spawn_terminal_forwarding(connection, terminal).await;
|
||||
@@ -265,11 +248,7 @@ impl Resolve<super::Args> for DisconnectTerminal {
|
||||
self,
|
||||
args: &super::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
if let Some(channel) =
|
||||
terminal_channels().remove(&self.channel).await
|
||||
{
|
||||
channel.cancel.cancel();
|
||||
}
|
||||
terminal_channels().remove(&self.channel).await;
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
@@ -288,18 +267,12 @@ impl Resolve<super::Args> for ExecuteTerminal {
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
|
||||
if periphery_config().disable_terminals {
|
||||
return Err(anyhow!(
|
||||
"Terminals are disabled in the Periphery config"
|
||||
));
|
||||
}
|
||||
|
||||
let channel =
|
||||
core_connections().get(&args.core).await.with_context(
|
||||
|| format!("Failed to find channel for {}", args.core),
|
||||
)?;
|
||||
|
||||
let terminal = get_terminal(&self.terminal).await?;
|
||||
let terminal = get_terminal(&self.terminal, &self.target).await?;
|
||||
|
||||
let channel_id = Uuid::new_v4();
|
||||
|
||||
@@ -323,82 +296,10 @@ impl Resolve<super::Args> for ExecuteTerminal {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for ExecuteContainerExec {
|
||||
#[instrument(
|
||||
"ExecuteContainerExec",
|
||||
skip_all,
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
container = self.container,
|
||||
shell = self.shell,
|
||||
command = self.command,
|
||||
recreate = format!("{:?}", self.recreate)
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
|
||||
if periphery_config().disable_container_terminals {
|
||||
return Err(anyhow!(
|
||||
"Container Terminals are disabled in the Periphery config"
|
||||
));
|
||||
}
|
||||
|
||||
let Self {
|
||||
container,
|
||||
shell,
|
||||
command,
|
||||
recreate,
|
||||
} = self;
|
||||
|
||||
if container.contains("&&") || shell.contains("&&") {
|
||||
return Err(anyhow!(
|
||||
"The use of '&&' is forbidden in the container name or shell"
|
||||
));
|
||||
}
|
||||
|
||||
let channel =
|
||||
core_connections().get(&args.core).await.with_context(
|
||||
|| format!("Failed to find channel for {}", args.core),
|
||||
)?;
|
||||
|
||||
let terminal = create_terminal(
|
||||
container.clone(),
|
||||
Some(format!("docker exec -it {container} {shell}")),
|
||||
recreate,
|
||||
Some((container, ContainerTerminalMode::Exec)),
|
||||
)
|
||||
.await
|
||||
.context("Failed to create terminal for container exec")?;
|
||||
|
||||
// Wait a bit for terminal to initialize
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
let channel_id = Uuid::new_v4();
|
||||
|
||||
let stdout = setup_execute_command_on_terminal(
|
||||
channel_id, &terminal, &command,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
forward_execute_command_on_terminal_response(
|
||||
&channel.sender,
|
||||
channel_id,
|
||||
stdout,
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
Ok(channel_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument("SpawnTerminalForwarding", skip_all)]
|
||||
async fn spawn_terminal_forwarding(
|
||||
connection: Arc<BufferedChannel<EncodedTransportMessage>>,
|
||||
terminal: Arc<Terminal>,
|
||||
terminal: Arc<PeripheryTerminal>,
|
||||
) -> Uuid {
|
||||
let channel = Uuid::new_v4();
|
||||
let cancel = CancellationToken::new();
|
||||
@@ -430,7 +331,7 @@ async fn spawn_terminal_forwarding(
|
||||
async fn handle_terminal_forwarding(
|
||||
sender: &Sender<EncodedTransportMessage>,
|
||||
channel: Uuid,
|
||||
terminal: Arc<Terminal>,
|
||||
terminal: Arc<PeripheryTerminal>,
|
||||
cancel: CancellationToken,
|
||||
) {
|
||||
// This waits to begin forwarding until Core sends the None byte start trigger.
|
||||
@@ -470,23 +371,15 @@ async fn handle_terminal_forwarding(
|
||||
|
||||
// Forward stdout -> WS
|
||||
let mut stdout = terminal.stdout.resubscribe();
|
||||
|
||||
loop {
|
||||
let res = tokio::select! {
|
||||
res = stdout.recv() => res,
|
||||
_ = terminal.cancel.cancelled() => {
|
||||
let _ = sender.send_terminal(channel, Err(anyhow!(
|
||||
"\n{} {}",
|
||||
"pty".bold(),
|
||||
"exited".red().bold()
|
||||
))).await;
|
||||
let _ = sender.send_terminal_exited(channel).await;
|
||||
break
|
||||
},
|
||||
_ = cancel.cancelled() => {
|
||||
let _ = sender.send_terminal(channel, Err(anyhow!(
|
||||
"\n{} {}",
|
||||
"websocket".bold(),
|
||||
"disconnected".red().bold()
|
||||
))).await;
|
||||
break
|
||||
}
|
||||
};
|
||||
@@ -494,17 +387,7 @@ async fn handle_terminal_forwarding(
|
||||
let bytes = match res {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_e) => {
|
||||
terminal.cancel();
|
||||
let _ = sender
|
||||
.send_terminal(
|
||||
channel,
|
||||
Err(anyhow!(
|
||||
"\n{} {}",
|
||||
"pty".bold(),
|
||||
"exited".red().bold()
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
let _ = sender.send_terminal_exited(channel).await;
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -519,12 +402,7 @@ async fn handle_terminal_forwarding(
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if let Some(terminal_channel) =
|
||||
terminal_channels().remove(&channel).await
|
||||
{
|
||||
trace!("Cancel called for {channel}");
|
||||
terminal_channel.cancel.cancel();
|
||||
}
|
||||
terminal_channels().remove(&channel).await;
|
||||
clean_up_terminals().await;
|
||||
}
|
||||
|
||||
@@ -532,7 +410,7 @@ async fn handle_terminal_forwarding(
|
||||
#[instrument("SetupExecuteTerminal", skip(terminal))]
|
||||
async fn setup_execute_command_on_terminal(
|
||||
channel_id: Uuid,
|
||||
terminal: &Terminal,
|
||||
terminal: &PeripheryTerminal,
|
||||
command: &str,
|
||||
) -> anyhow::Result<
|
||||
impl Stream<Item = Result<String, LinesCodecError>> + 'static,
|
||||
@@ -555,7 +433,7 @@ async fn setup_execute_command_on_terminal(
|
||||
|
||||
terminal
|
||||
.stdin
|
||||
.send(StdinMsg::Bytes(full_command.into()))
|
||||
.send(TerminalStdinMessage::forward(full_command.into()))
|
||||
.await
|
||||
.context("Failed to send command to terminal stdin")?;
|
||||
|
||||
@@ -587,7 +465,7 @@ async fn forward_execute_command_on_terminal_response(
|
||||
channel: Uuid,
|
||||
mut stdout: impl Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
||||
) {
|
||||
// This waits to begin forwarding until Core sends the None byte start trigger.
|
||||
// This waits to begin forwarding until Core sends the Begin byte start trigger.
|
||||
// This ensures no messages are lost before channels on both sides are set up.
|
||||
if let Err(e) = terminal_triggers().recv(&channel).await {
|
||||
warn!("{e:#}");
|
||||
|
||||
@@ -171,7 +171,8 @@ async fn handle_onboarding(
|
||||
public_key_validator: core_public_keys(),
|
||||
socket: &mut socket,
|
||||
})
|
||||
.await?;
|
||||
.await
|
||||
.context("Onboarding failed")?;
|
||||
|
||||
// Post onboarding login 1: Send public key
|
||||
socket
|
||||
|
||||
@@ -81,11 +81,21 @@ async fn handle_socket<W: Websocket>(
|
||||
|
||||
let forward_writes = async {
|
||||
loop {
|
||||
let message = match receiver.recv().await {
|
||||
Ok(message) => message,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
break;
|
||||
let message = match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
receiver.recv(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => message,
|
||||
Ok(Err(_)) => break,
|
||||
// Handle sending Ping
|
||||
Err(_) => {
|
||||
if let Err(e) = ws_write.ping().await {
|
||||
warn!("Failed to send ping | {e:?}");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match ws_write.send(message.into_bytes()).await {
|
||||
@@ -93,11 +103,11 @@ async fn handle_socket<W: Websocket>(
|
||||
Ok(_) => receiver.clear_buffer(),
|
||||
Err(e) => {
|
||||
warn!("Failed to send response | {e:?}");
|
||||
let _ = ws_write.close().await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = ws_write.close().await;
|
||||
};
|
||||
|
||||
let handle_reads = async {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_standard_command;
|
||||
use komodo_client::entities::stack::ComposeProject;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
pub fn docker_compose() -> &'static str {
|
||||
if periphery_config().legacy_compose_cli {
|
||||
"docker-compose"
|
||||
} else {
|
||||
"docker compose"
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_compose_projects()
|
||||
-> anyhow::Result<Vec<ComposeProject>> {
|
||||
let docker_compose = docker_compose();
|
||||
let res = run_komodo_standard_command(
|
||||
"List Projects",
|
||||
None,
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(format!(
|
||||
"Failed to list compose projects using {docker_compose} ls"
|
||||
)));
|
||||
}
|
||||
|
||||
let res =
|
||||
serde_json::from_str::<Vec<DockerComposeLsItem>>(&res.stdout)
|
||||
.with_context(|| res.stdout.clone())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to parse '{docker_compose} ls' response from json"
|
||||
)
|
||||
})?
|
||||
.into_iter()
|
||||
.filter(|item| !item.name.is_empty())
|
||||
.map(|item| ComposeProject {
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
compose_files: item
|
||||
.config_files
|
||||
.split(',')
|
||||
.map(str::to_string)
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DockerComposeLsItem {
|
||||
#[serde(default, alias = "Name")]
|
||||
pub name: String,
|
||||
#[serde(alias = "Status")]
|
||||
pub status: Option<String>,
|
||||
/// Comma seperated list of paths
|
||||
#[serde(default, alias = "ConfigFiles")]
|
||||
pub config_files: String,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_standard_command;
|
||||
use komodo_client::entities::docker::config::{
|
||||
SwarmConfig, SwarmConfigListItem,
|
||||
};
|
||||
|
||||
pub async fn list_swarm_configs()
|
||||
-> anyhow::Result<Vec<SwarmConfigListItem>> {
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Configs",
|
||||
None,
|
||||
"docker config ls --format json",
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(format!(
|
||||
"Failed to list swarm configs using 'docker config ls'"
|
||||
)));
|
||||
}
|
||||
|
||||
// The output is in JSONL, need to convert to standard JSON vec.
|
||||
serde_json::from_str(&format!(
|
||||
"[{}]",
|
||||
res.stdout.trim().replace('\n', ",")
|
||||
))
|
||||
.context("Failed to parse 'docker config ls' response from json")
|
||||
}
|
||||
|
||||
pub async fn inspect_swarm_config(
|
||||
config: &str,
|
||||
) -> anyhow::Result<Vec<SwarmConfig>> {
|
||||
let res = run_komodo_standard_command(
|
||||
"Inspect Swarm Config",
|
||||
None,
|
||||
format!(r#"docker config inspect "{config}""#),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(format!(
|
||||
"Failed to inspect swarm config using 'docker config inspect {config}'"
|
||||
)));
|
||||
}
|
||||
|
||||
serde_json::from_str(&res.stdout).context(
|
||||
"Failed to parse 'docker config inspect' response from json",
|
||||
)
|
||||
}
|
||||
@@ -4,14 +4,14 @@ use anyhow::Context;
|
||||
use bollard::query_parameters::{
|
||||
InspectContainerOptions, ListContainersOptions,
|
||||
};
|
||||
use komodo_client::entities::docker::{
|
||||
ContainerConfig, GraphDriverData, HealthConfig, PortBinding,
|
||||
container::*,
|
||||
};
|
||||
use komodo_client::entities::docker::{container::*, *};
|
||||
|
||||
use crate::state::container_stats;
|
||||
|
||||
use super::DockerClient;
|
||||
use super::{
|
||||
DockerClient, convert_health_config, convert_mount,
|
||||
convert_mount_point_type, convert_resources_ulimits,
|
||||
};
|
||||
|
||||
impl DockerClient {
|
||||
pub async fn list_containers(
|
||||
@@ -257,11 +257,7 @@ impl DockerClient {
|
||||
.ulimits
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|ulimit| ResourcesUlimits {
|
||||
name: ulimit.name,
|
||||
soft: ulimit.soft,
|
||||
hard: ulimit.hard,
|
||||
})
|
||||
.map(convert_resources_ulimits)
|
||||
.collect(),
|
||||
cpu_count: config.cpu_count,
|
||||
cpu_percent: config.cpu_percent,
|
||||
@@ -309,49 +305,7 @@ impl DockerClient {
|
||||
.mounts
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|mount| ContainerMount {
|
||||
target: mount.target,
|
||||
source: mount.source,
|
||||
typ: mount
|
||||
.typ
|
||||
.map(convert_mount_type)
|
||||
.unwrap_or_default(),
|
||||
read_only: mount.read_only,
|
||||
consistency: mount.consistency,
|
||||
bind_options: mount.bind_options.map(|options| {
|
||||
MountBindOptions {
|
||||
propagation: options
|
||||
.propagation
|
||||
.map(convert_mount_propogation)
|
||||
.unwrap_or_default(),
|
||||
non_recursive: options.non_recursive,
|
||||
create_mountpoint: options.create_mountpoint,
|
||||
read_only_non_recursive: options
|
||||
.read_only_non_recursive,
|
||||
read_only_force_recursive: options
|
||||
.read_only_force_recursive,
|
||||
}
|
||||
}),
|
||||
volume_options: mount.volume_options.map(|options| {
|
||||
MountVolumeOptions {
|
||||
no_copy: options.no_copy,
|
||||
labels: options.labels.unwrap_or_default(),
|
||||
driver_config: options.driver_config.map(|config| {
|
||||
MountVolumeOptionsDriverConfig {
|
||||
name: config.name,
|
||||
options: config.options.unwrap_or_default(),
|
||||
}
|
||||
}),
|
||||
subpath: options.subpath,
|
||||
}
|
||||
}),
|
||||
tmpfs_options: mount.tmpfs_options.map(|options| {
|
||||
MountTmpfsOptions {
|
||||
size_bytes: options.size_bytes,
|
||||
mode: options.mode,
|
||||
}
|
||||
}),
|
||||
})
|
||||
.map(convert_mount)
|
||||
.collect(),
|
||||
console_size: config
|
||||
.console_size
|
||||
@@ -435,14 +389,7 @@ impl DockerClient {
|
||||
stdin_once: config.stdin_once,
|
||||
env: config.env.unwrap_or_default(),
|
||||
cmd: config.cmd.unwrap_or_default(),
|
||||
healthcheck: config.healthcheck.map(|health| HealthConfig {
|
||||
test: health.test.unwrap_or_default(),
|
||||
interval: health.interval,
|
||||
timeout: health.timeout,
|
||||
retries: health.retries,
|
||||
start_period: health.start_period,
|
||||
start_interval: health.start_interval,
|
||||
}),
|
||||
healthcheck: config.healthcheck.map(convert_health_config),
|
||||
args_escaped: config.args_escaped,
|
||||
image: config.image,
|
||||
volumes: config
|
||||
@@ -657,74 +604,6 @@ fn convert_restart_policy(
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_type(
|
||||
typ: bollard::secret::MountTypeEnum,
|
||||
) -> MountTypeEnum {
|
||||
match typ {
|
||||
bollard::secret::MountTypeEnum::EMPTY => MountTypeEnum::Empty,
|
||||
bollard::secret::MountTypeEnum::BIND => MountTypeEnum::Bind,
|
||||
bollard::secret::MountTypeEnum::VOLUME => MountTypeEnum::Volume,
|
||||
bollard::secret::MountTypeEnum::IMAGE => MountTypeEnum::Image,
|
||||
bollard::secret::MountTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
|
||||
bollard::secret::MountTypeEnum::NPIPE => MountTypeEnum::Npipe,
|
||||
bollard::secret::MountTypeEnum::CLUSTER => MountTypeEnum::Cluster,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_point_type(
|
||||
typ: bollard::secret::MountPointTypeEnum,
|
||||
) -> MountTypeEnum {
|
||||
match typ {
|
||||
bollard::secret::MountPointTypeEnum::EMPTY => {
|
||||
MountTypeEnum::Empty
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::BIND => MountTypeEnum::Bind,
|
||||
bollard::secret::MountPointTypeEnum::VOLUME => {
|
||||
MountTypeEnum::Volume
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::IMAGE => {
|
||||
MountTypeEnum::Image
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::TMPFS => {
|
||||
MountTypeEnum::Tmpfs
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::NPIPE => {
|
||||
MountTypeEnum::Npipe
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::CLUSTER => {
|
||||
MountTypeEnum::Cluster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_propogation(
|
||||
propogation: bollard::secret::MountBindOptionsPropagationEnum,
|
||||
) -> MountBindOptionsPropagationEnum {
|
||||
match propogation {
|
||||
bollard::secret::MountBindOptionsPropagationEnum::EMPTY => {
|
||||
MountBindOptionsPropagationEnum::Empty
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::PRIVATE => {
|
||||
MountBindOptionsPropagationEnum::Private
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RPRIVATE => {
|
||||
MountBindOptionsPropagationEnum::Rprivate
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::SHARED => {
|
||||
MountBindOptionsPropagationEnum::Shared
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RSHARED => {
|
||||
MountBindOptionsPropagationEnum::Rshared
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::SLAVE => {
|
||||
MountBindOptionsPropagationEnum::Slave
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RSLAVE => {
|
||||
MountBindOptionsPropagationEnum::Rslave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_cgroupns_mode(
|
||||
mode: bollard::secret::HostConfigCgroupnsModeEnum,
|
||||
) -> HostConfigCgroupnsModeEnum {
|
||||
+437
-10
@@ -1,15 +1,26 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use bollard::Docker;
|
||||
use command::run_komodo_command;
|
||||
use komodo_client::entities::{TerminationSignal, update::Log};
|
||||
use run_command::async_run_command;
|
||||
use command::{run_komodo_standard_command, run_shell_command};
|
||||
use komodo_client::entities::{
|
||||
TerminationSignal,
|
||||
docker::{task::*, *},
|
||||
update::Log,
|
||||
};
|
||||
|
||||
pub mod compose;
|
||||
pub mod config;
|
||||
pub mod stack;
|
||||
pub mod stats;
|
||||
|
||||
mod containers;
|
||||
mod images;
|
||||
mod networks;
|
||||
mod volumes;
|
||||
mod container;
|
||||
mod image;
|
||||
mod network;
|
||||
mod node;
|
||||
mod secret;
|
||||
mod service;
|
||||
mod swarm;
|
||||
mod task;
|
||||
mod volume;
|
||||
|
||||
pub struct DockerClient {
|
||||
docker: Docker,
|
||||
@@ -38,9 +49,9 @@ pub async fn docker_login(
|
||||
Some(token) => token,
|
||||
None => crate::helpers::registry_token(domain, account)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
let log = run_shell_command(&format!(
|
||||
"echo {registry_token} | docker login {domain} --username '{account}' --password-stdin",
|
||||
))
|
||||
), None)
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
@@ -63,7 +74,7 @@ pub async fn docker_login(
|
||||
#[instrument("PullImage")]
|
||||
pub async fn pull_image(image: &str) -> Log {
|
||||
let command = format!("docker pull {image}");
|
||||
run_komodo_command("Docker Pull", None, command).await
|
||||
run_komodo_standard_command("Docker Pull", None, command).await
|
||||
}
|
||||
|
||||
pub fn stop_container_command(
|
||||
@@ -79,3 +90,419 @@ pub fn stop_container_command(
|
||||
.unwrap_or_default();
|
||||
format!("docker stop{signal}{time} {container_name}")
|
||||
}
|
||||
|
||||
fn convert_object_version(
|
||||
version: bollard::models::ObjectVersion,
|
||||
) -> ObjectVersion {
|
||||
ObjectVersion {
|
||||
index: version.index,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_driver(driver: bollard::models::Driver) -> Driver {
|
||||
Driver {
|
||||
name: driver.name,
|
||||
options: driver.options,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount(mount: bollard::models::Mount) -> Mount {
|
||||
Mount {
|
||||
target: mount.target,
|
||||
source: mount.source,
|
||||
typ: mount.typ.map(convert_mount_type).unwrap_or_default(),
|
||||
read_only: mount.read_only,
|
||||
consistency: mount.consistency,
|
||||
bind_options: mount.bind_options.map(|options| {
|
||||
MountBindOptions {
|
||||
propagation: options
|
||||
.propagation
|
||||
.map(convert_mount_propogation)
|
||||
.unwrap_or_default(),
|
||||
non_recursive: options.non_recursive,
|
||||
create_mountpoint: options.create_mountpoint,
|
||||
read_only_non_recursive: options.read_only_non_recursive,
|
||||
read_only_force_recursive: options.read_only_force_recursive,
|
||||
}
|
||||
}),
|
||||
volume_options: mount.volume_options.map(|options| {
|
||||
MountVolumeOptions {
|
||||
no_copy: options.no_copy,
|
||||
labels: options.labels.unwrap_or_default(),
|
||||
driver_config: options.driver_config.map(|config| {
|
||||
MountVolumeOptionsDriverConfig {
|
||||
name: config.name,
|
||||
options: config.options.unwrap_or_default(),
|
||||
}
|
||||
}),
|
||||
subpath: options.subpath,
|
||||
}
|
||||
}),
|
||||
tmpfs_options: mount.tmpfs_options.map(|options| {
|
||||
MountTmpfsOptions {
|
||||
size_bytes: options.size_bytes,
|
||||
mode: options.mode,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_type(
|
||||
typ: bollard::secret::MountTypeEnum,
|
||||
) -> MountTypeEnum {
|
||||
match typ {
|
||||
bollard::secret::MountTypeEnum::EMPTY => MountTypeEnum::Empty,
|
||||
bollard::secret::MountTypeEnum::BIND => MountTypeEnum::Bind,
|
||||
bollard::secret::MountTypeEnum::VOLUME => MountTypeEnum::Volume,
|
||||
bollard::secret::MountTypeEnum::IMAGE => MountTypeEnum::Image,
|
||||
bollard::secret::MountTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
|
||||
bollard::secret::MountTypeEnum::NPIPE => MountTypeEnum::Npipe,
|
||||
bollard::secret::MountTypeEnum::CLUSTER => MountTypeEnum::Cluster,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_propogation(
|
||||
propogation: bollard::secret::MountBindOptionsPropagationEnum,
|
||||
) -> MountBindOptionsPropagationEnum {
|
||||
match propogation {
|
||||
bollard::secret::MountBindOptionsPropagationEnum::EMPTY => {
|
||||
MountBindOptionsPropagationEnum::Empty
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::PRIVATE => {
|
||||
MountBindOptionsPropagationEnum::Private
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RPRIVATE => {
|
||||
MountBindOptionsPropagationEnum::Rprivate
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::SHARED => {
|
||||
MountBindOptionsPropagationEnum::Shared
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RSHARED => {
|
||||
MountBindOptionsPropagationEnum::Rshared
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::SLAVE => {
|
||||
MountBindOptionsPropagationEnum::Slave
|
||||
}
|
||||
bollard::secret::MountBindOptionsPropagationEnum::RSLAVE => {
|
||||
MountBindOptionsPropagationEnum::Rslave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mount_point_type(
|
||||
typ: bollard::secret::MountPointTypeEnum,
|
||||
) -> MountTypeEnum {
|
||||
match typ {
|
||||
bollard::secret::MountPointTypeEnum::EMPTY => {
|
||||
MountTypeEnum::Empty
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::BIND => MountTypeEnum::Bind,
|
||||
bollard::secret::MountPointTypeEnum::VOLUME => {
|
||||
MountTypeEnum::Volume
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::IMAGE => {
|
||||
MountTypeEnum::Image
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::TMPFS => {
|
||||
MountTypeEnum::Tmpfs
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::NPIPE => {
|
||||
MountTypeEnum::Npipe
|
||||
}
|
||||
bollard::secret::MountPointTypeEnum::CLUSTER => {
|
||||
MountTypeEnum::Cluster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_health_config(
|
||||
config: bollard::models::HealthConfig,
|
||||
) -> HealthConfig {
|
||||
HealthConfig {
|
||||
test: config.test.unwrap_or_default(),
|
||||
interval: config.interval,
|
||||
timeout: config.timeout,
|
||||
retries: config.retries,
|
||||
start_period: config.start_period,
|
||||
start_interval: config.start_interval,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_resources_ulimits(
|
||||
ulimit: bollard::models::ResourcesUlimits,
|
||||
) -> ResourcesUlimits {
|
||||
ResourcesUlimits {
|
||||
name: ulimit.name,
|
||||
soft: ulimit.soft,
|
||||
hard: ulimit.hard,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_resource_object(
|
||||
object: bollard::models::ResourceObject,
|
||||
) -> ResourceObject {
|
||||
ResourceObject {
|
||||
nano_cpus: object.nano_cpus,
|
||||
memory_bytes: object.memory_bytes,
|
||||
generic_resources: object
|
||||
.generic_resources
|
||||
.map(convert_generic_resources),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_generic_resources(
|
||||
resources: Vec<bollard::models::GenericResourcesInner>,
|
||||
) -> Vec<GenericResourcesInner> {
|
||||
resources
|
||||
.into_iter()
|
||||
.map(|resource| GenericResourcesInner {
|
||||
named_resource_spec: resource.named_resource_spec.map(|spec| {
|
||||
GenericResourcesInnerNamedResourceSpec {
|
||||
kind: spec.kind,
|
||||
value: spec.value,
|
||||
}
|
||||
}),
|
||||
discrete_resource_spec: resource.discrete_resource_spec.map(
|
||||
|spec| GenericResourcesInnerDiscreteResourceSpec {
|
||||
kind: spec.kind,
|
||||
value: spec.value,
|
||||
},
|
||||
),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn convert_platform(platform: bollard::models::Platform) -> Platform {
|
||||
Platform {
|
||||
architecture: platform.architecture,
|
||||
os: platform.os,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_endpoint_spec_ports(
|
||||
ports: Vec<bollard::models::EndpointPortConfig>,
|
||||
) -> Vec<EndpointPortConfig> {
|
||||
ports
|
||||
.into_iter()
|
||||
.map(|port| EndpointPortConfig {
|
||||
name: port.name,
|
||||
protocol: port.protocol.map(|protocol| match protocol {
|
||||
bollard::secret::EndpointPortConfigProtocolEnum::EMPTY => EndpointPortConfigProtocolEnum::EMPTY,
|
||||
bollard::secret::EndpointPortConfigProtocolEnum::TCP => EndpointPortConfigProtocolEnum::TCP,
|
||||
bollard::secret::EndpointPortConfigProtocolEnum::UDP => EndpointPortConfigProtocolEnum::UDP,
|
||||
bollard::secret::EndpointPortConfigProtocolEnum::SCTP => EndpointPortConfigProtocolEnum::SCTP,
|
||||
}),
|
||||
target_port: port.target_port,
|
||||
published_port: port.published_port,
|
||||
publish_mode: port.publish_mode.map(|protocol| match protocol {
|
||||
bollard::secret::EndpointPortConfigPublishModeEnum::EMPTY => EndpointPortConfigPublishModeEnum::EMPTY,
|
||||
bollard::secret::EndpointPortConfigPublishModeEnum::INGRESS => EndpointPortConfigPublishModeEnum::INGRESS,
|
||||
bollard::secret::EndpointPortConfigPublishModeEnum::HOST => EndpointPortConfigPublishModeEnum::HOST,
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn convert_task_spec(spec: bollard::models::TaskSpec) -> TaskSpec {
|
||||
TaskSpec {
|
||||
plugin_spec: spec.plugin_spec.map(|spec| TaskSpecPluginSpec {
|
||||
name: spec.name,
|
||||
remote: spec.remote,
|
||||
disabled: spec.disabled,
|
||||
plugin_privilege: spec.plugin_privilege.map(|privileges| {
|
||||
privileges
|
||||
.into_iter()
|
||||
.map(|privilege| PluginPrivilege {
|
||||
name: privilege.name,
|
||||
description: privilege.description,
|
||||
value: privilege.value,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
}),
|
||||
container_spec: spec
|
||||
.container_spec
|
||||
.map(convert_task_spec_container_spec),
|
||||
network_attachment_spec: spec.network_attachment_spec.map(
|
||||
|spec| TaskSpecNetworkAttachmentSpec {
|
||||
container_id: spec.container_id,
|
||||
},
|
||||
),
|
||||
resources: spec.resources.map(|resources| TaskSpecResources {
|
||||
limits: resources.limits.map(|limits| Limit {
|
||||
nano_cpus: limits.nano_cpus,
|
||||
memory_bytes: limits.memory_bytes,
|
||||
pids: limits.pids,
|
||||
}),
|
||||
reservations: resources
|
||||
.reservations
|
||||
.map(convert_resource_object),
|
||||
}),
|
||||
restart_policy: spec.restart_policy.map(|policy| {
|
||||
TaskSpecRestartPolicy {
|
||||
condition: policy
|
||||
.condition
|
||||
.map(convert_task_spec_restart_policy_condition),
|
||||
delay: policy.delay,
|
||||
max_attempts: policy.max_attempts,
|
||||
window: policy.window,
|
||||
}
|
||||
}),
|
||||
placement: spec.placement.map(|placement| TaskSpecPlacement {
|
||||
constraints: placement.constraints,
|
||||
preferences: placement.preferences.map(|preferences| {
|
||||
preferences
|
||||
.into_iter()
|
||||
.map(|preference| TaskSpecPlacementPreferences {
|
||||
spread: preference.spread.map(|spread| {
|
||||
TaskSpecPlacementSpread {
|
||||
spread_descriptor: spread.spread_descriptor,
|
||||
}
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
max_replicas: placement.max_replicas,
|
||||
platforms: placement.platforms.map(|platforms| {
|
||||
platforms.into_iter().map(convert_platform).collect()
|
||||
}),
|
||||
}),
|
||||
force_update: spec.force_update,
|
||||
runtime: spec.runtime,
|
||||
networks: spec.networks.map(|networks| {
|
||||
networks
|
||||
.into_iter()
|
||||
.map(|network| NetworkAttachmentConfig {
|
||||
target: network.target,
|
||||
aliases: network.aliases,
|
||||
driver_opts: network.driver_opts,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
log_driver: spec.log_driver.map(|driver| TaskSpecLogDriver {
|
||||
name: driver.name,
|
||||
options: driver.options,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_task_spec_container_spec(
|
||||
spec: bollard::models::TaskSpecContainerSpec,
|
||||
) -> TaskSpecContainerSpec {
|
||||
TaskSpecContainerSpec {
|
||||
image: spec.image,
|
||||
labels: spec.labels,
|
||||
command: spec.command,
|
||||
args: spec.args,
|
||||
hostname: spec.hostname,
|
||||
env: spec.env,
|
||||
dir: spec.dir,
|
||||
user: spec.user,
|
||||
groups: spec.groups,
|
||||
privileges: spec.privileges.map(|privilege| {
|
||||
TaskSpecContainerSpecPrivileges {
|
||||
credential_spec: privilege.credential_spec.map(|spec| {
|
||||
TaskSpecContainerSpecPrivilegesCredentialSpec {
|
||||
config: spec.config,
|
||||
file: spec.file,
|
||||
registry: spec.registry,
|
||||
}
|
||||
}),
|
||||
se_linux_context: privilege.se_linux_context.map(|context| {
|
||||
TaskSpecContainerSpecPrivilegesSeLinuxContext {
|
||||
disable: context.disable,
|
||||
user: context.user,
|
||||
role: context.role,
|
||||
typ: context.typ,
|
||||
level: context.level,
|
||||
}
|
||||
}),
|
||||
seccomp: privilege.seccomp.map(|seccomp| {
|
||||
TaskSpecContainerSpecPrivilegesSeccomp {
|
||||
mode: seccomp.mode.map(|mode| match mode {
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::EMPTY => TaskSpecContainerSpecPrivilegesSeccompModeEnum::EMPTY,
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::DEFAULT => TaskSpecContainerSpecPrivilegesSeccompModeEnum::DEFAULT,
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::UNCONFINED => TaskSpecContainerSpecPrivilegesSeccompModeEnum::UNCONFINED,
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::CUSTOM => TaskSpecContainerSpecPrivilegesSeccompModeEnum::CUSTOM,
|
||||
}),
|
||||
profile: seccomp.profile,
|
||||
}
|
||||
}),
|
||||
app_armor: privilege.app_armor.map(|app_armor| {
|
||||
TaskSpecContainerSpecPrivilegesAppArmor {
|
||||
mode: app_armor.mode.map(|mode| match mode {
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::EMPTY => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::EMPTY,
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DEFAULT => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DEFAULT,
|
||||
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DISABLED => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DISABLED,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
no_new_privileges: privilege.no_new_privileges,
|
||||
}
|
||||
}),
|
||||
tty: spec.tty,
|
||||
open_stdin: spec.open_stdin,
|
||||
read_only: spec.read_only,
|
||||
mounts: spec.mounts.map(|mounts| mounts.into_iter().map(convert_mount).collect()),
|
||||
stop_signal: spec.stop_signal,
|
||||
stop_grace_period: spec.stop_grace_period,
|
||||
health_check: spec.health_check.map(convert_health_config),
|
||||
hosts: spec.hosts,
|
||||
dns_config: spec.dns_config.map(|config| TaskSpecContainerSpecDnsConfig {
|
||||
nameservers: config.nameservers,
|
||||
search: config.search,
|
||||
options: config.options,
|
||||
}),
|
||||
secrets: spec.secrets.map(|secrets| secrets.into_iter().map(|secret| TaskSpecContainerSpecSecrets {
|
||||
file: secret.file.map(|file| TaskSpecContainerSpecFile {
|
||||
name: file.name,
|
||||
uid: file.uid,
|
||||
gid: file.gid,
|
||||
mode: file.mode,
|
||||
}),
|
||||
secret_id: secret.secret_id,
|
||||
secret_name: secret.secret_name,
|
||||
}).collect()),
|
||||
oom_score_adj: spec.oom_score_adj,
|
||||
configs: spec.configs.map(|configs| configs.into_iter().map(|config| TaskSpecContainerSpecConfigs {
|
||||
file: config.file.map(|file| TaskSpecContainerSpecFile {
|
||||
name: file.name,
|
||||
uid: file.uid,
|
||||
gid: file.gid,
|
||||
mode: file.mode,
|
||||
}),
|
||||
config_id: config.config_id,
|
||||
config_name: config.config_name,
|
||||
}).collect()),
|
||||
isolation: spec.isolation.map(|isolation| match isolation {
|
||||
bollard::secret::TaskSpecContainerSpecIsolationEnum::DEFAULT => TaskSpecContainerSpecIsolationEnum::DEFAULT,
|
||||
bollard::secret::TaskSpecContainerSpecIsolationEnum::PROCESS => TaskSpecContainerSpecIsolationEnum::PROCESS,
|
||||
bollard::secret::TaskSpecContainerSpecIsolationEnum::HYPERV => TaskSpecContainerSpecIsolationEnum::HYPERV,
|
||||
bollard::secret::TaskSpecContainerSpecIsolationEnum::EMPTY => TaskSpecContainerSpecIsolationEnum::EMPTY,
|
||||
}),
|
||||
init: spec.init,
|
||||
sysctls: spec.sysctls,
|
||||
capability_add: spec.capability_add,
|
||||
capability_drop: spec.capability_drop,
|
||||
ulimits: spec.ulimits.map(|ulimits| ulimits.into_iter().map(convert_resources_ulimits).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_task_spec_restart_policy_condition(
|
||||
condition: bollard::secret::TaskSpecRestartPolicyConditionEnum,
|
||||
) -> TaskSpecRestartPolicyConditionEnum {
|
||||
match condition {
|
||||
bollard::secret::TaskSpecRestartPolicyConditionEnum::EMPTY => TaskSpecRestartPolicyConditionEnum::EMPTY,
|
||||
bollard::secret::TaskSpecRestartPolicyConditionEnum::NONE => TaskSpecRestartPolicyConditionEnum::NONE,
|
||||
bollard::secret::TaskSpecRestartPolicyConditionEnum::ON_FAILURE => TaskSpecRestartPolicyConditionEnum::ON_FAILURE,
|
||||
bollard::secret::TaskSpecRestartPolicyConditionEnum::ANY => TaskSpecRestartPolicyConditionEnum::ANY,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_tls_info(tls_info: bollard::models::TlsInfo) -> TlsInfo {
|
||||
TlsInfo {
|
||||
trust_root: tls_info.trust_root,
|
||||
cert_issuer_subject: tls_info.cert_issuer_subject,
|
||||
cert_issuer_public_key: tls_info.cert_issuer_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
use anyhow::Context;
|
||||
use bollard::query_parameters::ListNodesOptions;
|
||||
use komodo_client::entities::docker::node::*;
|
||||
|
||||
use super::{
|
||||
DockerClient, convert_platform, convert_resource_object,
|
||||
};
|
||||
|
||||
impl DockerClient {
|
||||
/// Lists swarm nodes
|
||||
pub async fn list_swarm_nodes(
|
||||
&self,
|
||||
) -> anyhow::Result<Vec<SwarmNodeListItem>> {
|
||||
let nodes = self
|
||||
.docker
|
||||
.list_nodes(Option::<ListNodesOptions>::None)
|
||||
.await
|
||||
.context("Failed to query for swarm node list")?
|
||||
.into_iter()
|
||||
.map(convert_node_list_item)
|
||||
.collect();
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// Inspect a swarm node
|
||||
pub async fn inspect_swarm_node(
|
||||
&self,
|
||||
node_name: &str,
|
||||
) -> anyhow::Result<SwarmNode> {
|
||||
self
|
||||
.docker
|
||||
.inspect_node(node_name)
|
||||
.await
|
||||
.map(convert_node)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to query for swarm node with name {node_name}"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_node_list_item(
|
||||
node: bollard::models::Node,
|
||||
) -> SwarmNodeListItem {
|
||||
let (name, role, availability) = node
|
||||
.spec
|
||||
.map(|spec| {
|
||||
(
|
||||
spec.name,
|
||||
spec.role.map(convert_role),
|
||||
spec.availability.map(convert_availability),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
SwarmNodeListItem {
|
||||
id: node.id,
|
||||
name,
|
||||
role,
|
||||
availability,
|
||||
hostname: node
|
||||
.description
|
||||
.and_then(|description| description.hostname),
|
||||
state: node
|
||||
.status
|
||||
.and_then(|status| status.state.map(convert_state)),
|
||||
created_at: node.created_at,
|
||||
updated_at: node.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_node(node: bollard::models::Node) -> SwarmNode {
|
||||
SwarmNode {
|
||||
id: node.id,
|
||||
version: node.version.map(super::convert_object_version),
|
||||
created_at: node.created_at,
|
||||
updated_at: node.updated_at,
|
||||
spec: node.spec.map(convert_node_spec),
|
||||
description: node.description.map(|description| {
|
||||
NodeDescription {
|
||||
hostname: description.hostname,
|
||||
platform: description.platform.map(convert_platform),
|
||||
resources: description.resources.map(convert_resource_object),
|
||||
engine: description.engine.map(|engine| EngineDescription {
|
||||
engine_version: engine.engine_version,
|
||||
labels: engine.labels,
|
||||
plugins: engine.plugins.map(|plugins| {
|
||||
plugins
|
||||
.into_iter()
|
||||
.map(|plugin| EngineDescriptionPlugins {
|
||||
typ: plugin.typ,
|
||||
name: plugin.name,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
}),
|
||||
tls_info: description.tls_info.map(super::convert_tls_info),
|
||||
}
|
||||
}),
|
||||
status: node.status.map(|status| NodeStatus {
|
||||
state: status.state.map(convert_state),
|
||||
message: status.message,
|
||||
addr: status.addr,
|
||||
}),
|
||||
manager_status: node.manager_status.map(|manager_status| {
|
||||
ManagerStatus {
|
||||
leader: manager_status.leader,
|
||||
reachability: manager_status.reachability.map(
|
||||
|reachability| match reachability {
|
||||
bollard::secret::Reachability::UNKNOWN => {
|
||||
NodeReachability::UNKNOWN
|
||||
}
|
||||
bollard::secret::Reachability::UNREACHABLE => {
|
||||
NodeReachability::UNREACHABLE
|
||||
}
|
||||
bollard::secret::Reachability::REACHABLE => {
|
||||
NodeReachability::REACHABLE
|
||||
}
|
||||
},
|
||||
),
|
||||
addr: manager_status.addr,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_node_spec(spec: bollard::models::NodeSpec) -> NodeSpec {
|
||||
NodeSpec {
|
||||
name: spec.name,
|
||||
labels: spec.labels,
|
||||
role: spec.role.map(convert_role),
|
||||
availability: spec.availability.map(convert_availability),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_role(
|
||||
role: bollard::secret::NodeSpecRoleEnum,
|
||||
) -> NodeSpecRoleEnum {
|
||||
match role {
|
||||
bollard::secret::NodeSpecRoleEnum::EMPTY => {
|
||||
NodeSpecRoleEnum::EMPTY
|
||||
}
|
||||
bollard::secret::NodeSpecRoleEnum::WORKER => {
|
||||
NodeSpecRoleEnum::WORKER
|
||||
}
|
||||
bollard::secret::NodeSpecRoleEnum::MANAGER => {
|
||||
NodeSpecRoleEnum::MANAGER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_availability(
|
||||
availability: bollard::secret::NodeSpecAvailabilityEnum,
|
||||
) -> NodeSpecAvailabilityEnum {
|
||||
match availability {
|
||||
bollard::secret::NodeSpecAvailabilityEnum::EMPTY => {
|
||||
NodeSpecAvailabilityEnum::EMPTY
|
||||
}
|
||||
bollard::secret::NodeSpecAvailabilityEnum::ACTIVE => {
|
||||
NodeSpecAvailabilityEnum::ACTIVE
|
||||
}
|
||||
bollard::secret::NodeSpecAvailabilityEnum::PAUSE => {
|
||||
NodeSpecAvailabilityEnum::PAUSE
|
||||
}
|
||||
bollard::secret::NodeSpecAvailabilityEnum::DRAIN => {
|
||||
NodeSpecAvailabilityEnum::DRAIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_state(state: bollard::secret::NodeState) -> NodeState {
|
||||
match state {
|
||||
bollard::secret::NodeState::UNKNOWN => NodeState::UNKNOWN,
|
||||
bollard::secret::NodeState::DOWN => NodeState::DOWN,
|
||||
bollard::secret::NodeState::READY => NodeState::READY,
|
||||
bollard::secret::NodeState::DISCONNECTED => {
|
||||
NodeState::DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use anyhow::Context;
|
||||
use bollard::query_parameters::ListSecretsOptions;
|
||||
use komodo_client::entities::docker::secret::{
|
||||
SecretSpec, SwarmSecret, SwarmSecretListItem,
|
||||
};
|
||||
|
||||
use super::DockerClient;
|
||||
|
||||
impl DockerClient {
|
||||
pub async fn list_swarm_secrets(
|
||||
&self,
|
||||
) -> anyhow::Result<Vec<SwarmSecretListItem>> {
|
||||
let secrets = self
|
||||
.docker
|
||||
.list_secrets(Option::<ListSecretsOptions>::None)
|
||||
.await
|
||||
.context("Failed to query for swarm secret list")?
|
||||
.into_iter()
|
||||
.map(convert_secret_list_item)
|
||||
.collect();
|
||||
Ok(secrets)
|
||||
}
|
||||
|
||||
pub async fn inspect_swarm_secret(
|
||||
&self,
|
||||
secret_id: &str,
|
||||
) -> anyhow::Result<SwarmSecret> {
|
||||
self
|
||||
.docker
|
||||
.inspect_secret(secret_id)
|
||||
.await
|
||||
.map(convert_secret)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to query for swarm secret with id {secret_id}"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_secret_list_item(
|
||||
secret: bollard::models::Secret,
|
||||
) -> SwarmSecretListItem {
|
||||
let (name, driver, templating) = secret
|
||||
.spec
|
||||
.map(|spec| {
|
||||
(
|
||||
spec.name,
|
||||
spec.driver.map(|driver| driver.name),
|
||||
spec.templating.map(|driver| driver.name),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
SwarmSecretListItem {
|
||||
id: secret.id,
|
||||
name,
|
||||
driver,
|
||||
templating,
|
||||
created_at: secret.created_at,
|
||||
updated_at: secret.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_secret(secret: bollard::models::Secret) -> SwarmSecret {
|
||||
SwarmSecret {
|
||||
id: secret.id,
|
||||
version: secret.version.map(super::convert_object_version),
|
||||
created_at: secret.created_at,
|
||||
updated_at: secret.updated_at,
|
||||
spec: secret.spec.map(|spec| SecretSpec {
|
||||
name: spec.name,
|
||||
labels: spec.labels,
|
||||
data: spec.data,
|
||||
driver: spec.driver.map(super::convert_driver),
|
||||
templating: spec.templating.map(super::convert_driver),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
use anyhow::Context;
|
||||
use bollard::query_parameters::{
|
||||
InspectServiceOptions, ListServicesOptions,
|
||||
};
|
||||
use komodo_client::entities::docker::{
|
||||
NetworkAttachmentConfig, service::*,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl DockerClient {
|
||||
/// List swarm services
|
||||
pub async fn list_swarm_services(
|
||||
&self,
|
||||
) -> anyhow::Result<Vec<SwarmServiceListItem>> {
|
||||
let services = self
|
||||
.docker
|
||||
.list_services(Option::<ListServicesOptions>::None)
|
||||
.await
|
||||
.context("Failed to query for swarm service list")?
|
||||
.into_iter()
|
||||
.map(convert_service_list_item)
|
||||
.collect();
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
pub async fn inspect_swarm_service(
|
||||
&self,
|
||||
service_name: &str,
|
||||
) -> anyhow::Result<SwarmService> {
|
||||
self
|
||||
.docker
|
||||
.inspect_service(
|
||||
service_name,
|
||||
Some(InspectServiceOptions {
|
||||
insert_defaults: true,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map(convert_service)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to query for swarm service with name {service_name}"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_service_list_item(
|
||||
service: bollard::models::Service,
|
||||
) -> SwarmServiceListItem {
|
||||
let (name, (image, restart, runtime), replicas) = service
|
||||
.spec
|
||||
.map(|spec| {
|
||||
(
|
||||
spec.name,
|
||||
spec
|
||||
.task_template
|
||||
.map(|template| {
|
||||
(
|
||||
template.container_spec.and_then(|spec| spec.image),
|
||||
template.restart_policy.and_then(|policy| {
|
||||
policy
|
||||
.condition
|
||||
.map(convert_task_spec_restart_policy_condition)
|
||||
}),
|
||||
template.runtime,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
spec.mode.and_then(|mode| {
|
||||
mode.replicated.and_then(|replicated| replicated.replicas)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
SwarmServiceListItem {
|
||||
id: service.id,
|
||||
name,
|
||||
replicas,
|
||||
image,
|
||||
restart,
|
||||
runtime,
|
||||
created_at: service.created_at,
|
||||
updated_at: service.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_service(
|
||||
service: bollard::models::Service,
|
||||
) -> SwarmService {
|
||||
SwarmService {
|
||||
id: service.id,
|
||||
version: service.version.map(convert_object_version),
|
||||
created_at: service.created_at,
|
||||
updated_at: service.updated_at,
|
||||
spec: service.spec.map(|spec| ServiceSpec {
|
||||
name: spec.name,
|
||||
labels: spec.labels,
|
||||
task_template: spec.task_template.map(convert_task_spec),
|
||||
mode: spec.mode.map(|mode| ServiceSpecMode {
|
||||
replicated: mode.replicated.map(|replicated| ServiceSpecModeReplicated {
|
||||
replicas: replicated.replicas,
|
||||
}),
|
||||
// global: mode.global,
|
||||
replicated_job: mode.replicated_job.map(|job| ServiceSpecModeReplicatedJob {
|
||||
max_concurrent: job.max_concurrent,
|
||||
total_completions: job.total_completions,
|
||||
}),
|
||||
// global_job: mode.global_job,
|
||||
}),
|
||||
update_config: spec.update_config.map(|config| {
|
||||
ServiceSpecUpdateConfig {
|
||||
parallelism: config.parallelism,
|
||||
delay: config.delay,
|
||||
failure_action: config.failure_action.map(|action| match action {
|
||||
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::EMPTY => ServiceSpecUpdateConfigFailureActionEnum::EMPTY,
|
||||
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::CONTINUE => ServiceSpecUpdateConfigFailureActionEnum::CONTINUE,
|
||||
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::PAUSE => ServiceSpecUpdateConfigFailureActionEnum::PAUSE,
|
||||
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::ROLLBACK => ServiceSpecUpdateConfigFailureActionEnum::ROLLBACK,
|
||||
}),
|
||||
monitor: config.monitor,
|
||||
max_failure_ratio: config.max_failure_ratio,
|
||||
order: config.order.map(|order| match order {
|
||||
bollard::secret::ServiceSpecUpdateConfigOrderEnum::EMPTY => ServiceSpecUpdateConfigOrderEnum::EMPTY,
|
||||
bollard::secret::ServiceSpecUpdateConfigOrderEnum::STOP_FIRST => ServiceSpecUpdateConfigOrderEnum::STOP_FIRST,
|
||||
bollard::secret::ServiceSpecUpdateConfigOrderEnum::START_FIRST => ServiceSpecUpdateConfigOrderEnum::START_FIRST,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
rollback_config: spec.rollback_config.map(|config| {
|
||||
ServiceSpecRollbackConfig {
|
||||
parallelism: config.parallelism,
|
||||
delay: config.delay,
|
||||
failure_action: config.failure_action.map(|action| match action {
|
||||
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::EMPTY => ServiceSpecRollbackConfigFailureActionEnum::EMPTY,
|
||||
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::CONTINUE => ServiceSpecRollbackConfigFailureActionEnum::CONTINUE,
|
||||
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::PAUSE => ServiceSpecRollbackConfigFailureActionEnum::PAUSE,
|
||||
}),
|
||||
monitor: config.monitor,
|
||||
max_failure_ratio: config.max_failure_ratio,
|
||||
order: config.order.map(|order| match order {
|
||||
bollard::secret::ServiceSpecRollbackConfigOrderEnum::EMPTY => ServiceSpecRollbackConfigOrderEnum::EMPTY,
|
||||
bollard::secret::ServiceSpecRollbackConfigOrderEnum::STOP_FIRST => ServiceSpecRollbackConfigOrderEnum::STOP_FIRST,
|
||||
bollard::secret::ServiceSpecRollbackConfigOrderEnum::START_FIRST => ServiceSpecRollbackConfigOrderEnum::START_FIRST,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
networks: spec.networks.map(|networks| {
|
||||
networks
|
||||
.into_iter()
|
||||
.map(|network| NetworkAttachmentConfig {
|
||||
target: network.target,
|
||||
aliases: network.aliases,
|
||||
driver_opts: network.driver_opts,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
endpoint_spec: spec.endpoint_spec.map(convert_endpoint_spec),
|
||||
}),
|
||||
endpoint: service.endpoint.map(|endpoint| ServiceEndpoint {
|
||||
spec: endpoint.spec.map(convert_endpoint_spec),
|
||||
ports: endpoint.ports.map(convert_endpoint_spec_ports),
|
||||
virtual_ips: endpoint.virtual_ips.map(|ips| {
|
||||
ips
|
||||
.into_iter()
|
||||
.map(|ip| ServiceEndpointVirtualIps {
|
||||
network_id: ip.network_id,
|
||||
addr: ip.addr,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
}),
|
||||
update_status: service.update_status.map(|status| {
|
||||
ServiceUpdateStatus {
|
||||
state: status.state.map(convert_state),
|
||||
started_at: status.started_at,
|
||||
completed_at: status.completed_at,
|
||||
message: status.message,
|
||||
}
|
||||
}),
|
||||
service_status: service.service_status.map(|status| {
|
||||
ServiceServiceStatus {
|
||||
running_tasks: status.running_tasks,
|
||||
desired_tasks: status.desired_tasks,
|
||||
completed_tasks: status.completed_tasks,
|
||||
}
|
||||
}),
|
||||
job_status: service.job_status.map(|status| ServiceJobStatus {
|
||||
job_iteration: status.job_iteration.map(convert_object_version),
|
||||
last_execution: status.last_execution,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_endpoint_spec(
|
||||
spec: bollard::models::EndpointSpec,
|
||||
) -> EndpointSpec {
|
||||
EndpointSpec {
|
||||
mode: spec.mode.map(|mode| match mode {
|
||||
bollard::secret::EndpointSpecModeEnum::EMPTY => {
|
||||
EndpointSpecModeEnum::EMPTY
|
||||
}
|
||||
bollard::secret::EndpointSpecModeEnum::VIP => {
|
||||
EndpointSpecModeEnum::VIP
|
||||
}
|
||||
bollard::secret::EndpointSpecModeEnum::DNSRR => {
|
||||
EndpointSpecModeEnum::DNSRR
|
||||
}
|
||||
}),
|
||||
ports: spec.ports.map(convert_endpoint_spec_ports),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_state(
|
||||
state: bollard::secret::ServiceUpdateStatusStateEnum,
|
||||
) -> ServiceUpdateStatusStateEnum {
|
||||
match state {
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::EMPTY => ServiceUpdateStatusStateEnum::EMPTY,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::UPDATING => ServiceUpdateStatusStateEnum::UPDATING,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::PAUSED => ServiceUpdateStatusStateEnum::PAUSED,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::COMPLETED => ServiceUpdateStatusStateEnum::COMPLETED,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_STARTED => ServiceUpdateStatusStateEnum::ROLLBACK_STARTED,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_PAUSED => ServiceUpdateStatusStateEnum::ROLLBACK_PAUSED,
|
||||
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_COMPLETED => ServiceUpdateStatusStateEnum::ROLLBACK_COMPLETED,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_standard_command;
|
||||
use komodo_client::entities::docker::stack::{
|
||||
SwarmStackListItem, SwarmStackLists, SwarmStackServiceListItem,
|
||||
SwarmStackTaskListItem,
|
||||
};
|
||||
|
||||
pub async fn inspect_swarm_stack(
|
||||
name: String,
|
||||
) -> anyhow::Result<SwarmStackLists> {
|
||||
let (services, tasks) = tokio::try_join!(
|
||||
list_swarm_stack_services(&name),
|
||||
list_swarm_stack_tasks(&name),
|
||||
)?;
|
||||
Ok(SwarmStackLists {
|
||||
name,
|
||||
services,
|
||||
tasks,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_swarm_stacks()
|
||||
-> anyhow::Result<Vec<SwarmStackListItem>> {
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stacks",
|
||||
None,
|
||||
"docker stack ls --format json",
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(
|
||||
"Failed to list swarm stacks using 'docker stack ls'",
|
||||
));
|
||||
}
|
||||
|
||||
// The output is in JSONL, need to convert to standard JSON vec.
|
||||
serde_json::from_str(&format!(
|
||||
"[{}]",
|
||||
res.stdout.trim().replace('\n', ",")
|
||||
))
|
||||
.context("Failed to parse 'docker stack ls' response from json")
|
||||
}
|
||||
|
||||
pub async fn list_swarm_stack_services(
|
||||
stack: &str,
|
||||
) -> anyhow::Result<Vec<SwarmStackServiceListItem>> {
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stack Services",
|
||||
None,
|
||||
format!("docker stack services --format json {stack}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(
|
||||
"Failed to list swarm stacks using 'docker stack services'",
|
||||
));
|
||||
}
|
||||
|
||||
// The output is in JSONL, need to convert to standard JSON vec.
|
||||
serde_json::from_str(&format!(
|
||||
"[{}]",
|
||||
res.stdout.trim().replace('\n', ",")
|
||||
))
|
||||
.context(
|
||||
"Failed to parse 'docker stack services' response from json",
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list_swarm_stack_tasks(
|
||||
stack: &str,
|
||||
) -> anyhow::Result<Vec<SwarmStackTaskListItem>> {
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stack Tasks",
|
||||
None,
|
||||
format!("docker stack ps --format json {stack}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !res.success {
|
||||
return Err(anyhow!("{}", res.combined()).context(
|
||||
"Failed to list swarm stacks using 'docker stack ps'",
|
||||
));
|
||||
}
|
||||
|
||||
// The output is in JSONL, need to convert to standard JSON vec.
|
||||
serde_json::from_str(&format!(
|
||||
"[{}]",
|
||||
res.stdout.trim().replace('\n', ",")
|
||||
))
|
||||
.context("Failed to parse 'docker stack ps' response from json")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user