mirror of
https://github.com/moghtech/komodo.git
synced 2026-10-31 05:05:57 -05:00
Compare commits
97
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8c76b8487f | ||
|
|
2b32d9042a | ||
|
|
dc48f1f2ca | ||
|
|
8e7b7bdcf1 | ||
|
|
f11d64f72e | ||
|
|
2ffae85180 | ||
|
|
bd79d0f1e0 | ||
|
|
e890b1f675 | ||
|
|
3b7de25c30 | ||
|
|
793bb99f31 | ||
|
|
d465c9f273 | ||
|
|
ce641a8974 | ||
|
|
1b89ceb122 | ||
|
|
2dbc011d26 | ||
|
|
246da88ae1 | ||
|
|
a8c16f64b1 | ||
|
|
a5b711a348 | ||
|
|
9666e9ad83 | ||
|
|
7479640c73 | ||
|
|
4823825035 | ||
|
|
23897a7acf | ||
|
|
20d5588b5c | ||
|
|
f7e15ccde5 | ||
|
|
cf7623b1fc | ||
|
|
d3c464c05d | ||
|
|
5c9d416aa4 | ||
|
|
aabcd88312 | ||
|
|
9d2624c6bc | ||
|
|
ee11fb0b6c | ||
|
|
45adfbddd0 | ||
|
|
d26d035dc6 | ||
|
|
e673ba0adf | ||
|
|
f876facfa7 | ||
|
|
3a47d57478 | ||
|
|
a707028277 | ||
|
|
0c6276c677 | ||
|
|
fc9c6706f1 | ||
|
|
7674269ce9 | ||
|
|
3b511c5adc | ||
|
|
87221a10e9 | ||
|
|
450cb6a148 | ||
|
|
f252cefb21 | ||
|
|
7855e9d688 | ||
|
|
feb263c15f | ||
|
|
4f8d1c22cc | ||
|
|
60bd47834e | ||
|
|
4d632a6b61 | ||
|
|
381dd76723 | ||
|
|
077e28a5fe | ||
|
|
6b02aaed7d | ||
|
|
e466944c05 | ||
|
|
8ff94b7465 | ||
|
|
b17df5ed7b | ||
|
|
207dc30206 | ||
|
|
c3eb386bdb | ||
|
|
4279e46892 | ||
|
|
8d3d2fee12 |
Generated
+240
-143
@@ -2,15 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@@ -287,9 +278,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ec2"
|
||||
version = "1.173.0"
|
||||
version = "1.176.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daa0257ec1eb83217a286d91f836e7a1f82e885a6c890a8c9ff28a7f8dc71bf"
|
||||
checksum = "171b12a84d9c7b43b75bf2ae99e86ce40ff0b5ecc8194a67d547e55c1ad2438e"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -449,7 +440,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -680,7 +671,7 @@ dependencies = [
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -688,21 +679,6 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
@@ -885,7 +861,7 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.3.3",
|
||||
"hex",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"rand 0.9.2",
|
||||
@@ -926,7 +902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cache"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"tokio",
|
||||
@@ -1043,9 +1019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.49"
|
||||
version = "4.5.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1053,9 +1029,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.49"
|
||||
version = "4.5.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1118,11 +1094,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
"shlex",
|
||||
"svi",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1144,10 +1121,10 @@ checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1189,6 +1166,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -1233,6 +1219,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "croner"
|
||||
version = "3.0.0"
|
||||
@@ -1244,6 +1236,24 @@ dependencies = [
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@@ -1258,9 +1268,13 @@ checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"crossterm_winapi",
|
||||
"derive_more 2.0.1",
|
||||
"document-features",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -1429,7 +1443,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "database"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -1556,13 +1570,34 @@ version = "0.99.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"convert_case 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||
dependencies = [
|
||||
"convert_case 0.7.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_variants"
|
||||
version = "1.0.0"
|
||||
@@ -1707,7 +1742,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1749,7 +1784,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"formatting",
|
||||
@@ -1759,7 +1794,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -1855,7 +1890,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -2019,22 +2054,15 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cache",
|
||||
"command",
|
||||
"formatting",
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -2067,7 +2095,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2086,7 +2114,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2180,6 +2208,31 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.9.2",
|
||||
"ring",
|
||||
"thiserror 2.0.17",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.4"
|
||||
@@ -2188,7 +2241,7 @@ checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto",
|
||||
"hickory-proto 0.24.4",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
@@ -2201,6 +2254,27 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto 0.25.2",
|
||||
"ipconfig",
|
||||
"moka",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.9.2",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
@@ -2380,7 +2454,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -2593,9 +2667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.4"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
@@ -2614,24 +2688,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interpolate"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
"svi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipconfig"
|
||||
version = "0.3.2"
|
||||
@@ -2730,9 +2793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "10.0.0"
|
||||
version = "10.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1417155a38e99d7704ddb3ea7445fe57fdbd5d756d727740a9ed8b9ebaed6e1"
|
||||
checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"base64 0.22.1",
|
||||
@@ -2747,7 +2810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2755,6 +2818,7 @@ dependencies = [
|
||||
"colored",
|
||||
"comfy-table",
|
||||
"config",
|
||||
"crossterm",
|
||||
"database",
|
||||
"dotenvy",
|
||||
"environment_file",
|
||||
@@ -2767,25 +2831,27 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"wildcard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
"bson",
|
||||
"bytes",
|
||||
"clap",
|
||||
"derive_builder",
|
||||
"derive_default_builder",
|
||||
"derive_empty_traits",
|
||||
"derive_variants",
|
||||
"envy",
|
||||
"futures",
|
||||
"indexmap 2.11.4",
|
||||
"futures-util",
|
||||
"indexmap 2.12.0",
|
||||
"ipnetwork",
|
||||
"mongo_indexed",
|
||||
"partial_derive2",
|
||||
@@ -2804,12 +2870,11 @@ dependencies = [
|
||||
"tracing",
|
||||
"typeshare",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2839,11 +2904,11 @@ dependencies = [
|
||||
"environment_file",
|
||||
"envy",
|
||||
"formatting",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"git",
|
||||
"hex",
|
||||
"hmac",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"interpolate",
|
||||
"jsonwebtoken",
|
||||
"komodo_client",
|
||||
@@ -2856,10 +2921,11 @@ dependencies = [
|
||||
"reqwest",
|
||||
"resolver_api",
|
||||
"response",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"serde_yaml_ng",
|
||||
"serror",
|
||||
"sha2",
|
||||
@@ -2882,7 +2948,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2903,8 +2969,9 @@ dependencies = [
|
||||
"environment_file",
|
||||
"envy",
|
||||
"formatting",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"git",
|
||||
"hickory-resolver 0.25.2",
|
||||
"interpolate",
|
||||
"komodo_client",
|
||||
"logger",
|
||||
@@ -2912,14 +2979,14 @@ dependencies = [
|
||||
"periphery_client",
|
||||
"portable-pty",
|
||||
"resolver_api",
|
||||
"run_command",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml_ng",
|
||||
"serror",
|
||||
"shell-escape",
|
||||
"shlex",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -3002,7 +3069,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -3139,10 +3206,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"equivalent",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc_version",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mongo_indexed"
|
||||
version = "2.0.2"
|
||||
@@ -3196,14 +3282,14 @@ dependencies = [
|
||||
"bson",
|
||||
"chrono",
|
||||
"derive-where",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hickory-proto",
|
||||
"hickory-resolver",
|
||||
"hickory-proto 0.24.4",
|
||||
"hickory-resolver 0.24.4",
|
||||
"hmac",
|
||||
"macro_magic",
|
||||
"md-5",
|
||||
@@ -3214,7 +3300,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rustc_version_runtime",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
@@ -3275,7 +3361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "noise"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3422,20 +3508,15 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
@@ -3695,7 +3776,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_variants",
|
||||
@@ -3801,6 +3882,12 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.9.0"
|
||||
@@ -3909,7 +3996,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"socket2 0.6.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
@@ -3929,7 +4016,7 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.17",
|
||||
@@ -4121,7 +4208,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
@@ -4170,7 +4257,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "response"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4223,21 +4310,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-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -4290,9 +4362,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.32"
|
||||
version = "0.23.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
|
||||
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
@@ -4455,7 +4527,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "secret_file"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
@@ -4558,7 +4630,7 @@ version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -4639,7 +4711,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.0.4",
|
||||
"serde_core",
|
||||
@@ -4666,7 +4738,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
|
||||
dependencies = [
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -4755,6 +4827,27 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
@@ -4806,9 +4899,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "slack_client_rs"
|
||||
version = "1.2.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "295af69963fd1298940a45be3cefa447285f7cd71970f4a47e54be26771a7be6"
|
||||
checksum = "5e52d695d2a62bb62481567a1fafc9d83af9f2c469fa37872136e0171e6b3936"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@@ -5004,6 +5097,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
@@ -5132,29 +5231,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2 0.6.1",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5177,7 +5273,7 @@ version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -5201,7 +5297,7 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -5229,7 +5325,7 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -5321,7 +5417,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
@@ -5464,19 +5560,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "transport"
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
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.32",
|
||||
"rustls 0.23.34",
|
||||
"serde",
|
||||
"serror",
|
||||
"sha1",
|
||||
@@ -5507,7 +5604,7 @@ dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rustls 0.23.32",
|
||||
"rustls 0.23.34",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.17",
|
||||
|
||||
+13
-12
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.0.0-dev-59"
|
||||
version = "2.0.0-dev-86"
|
||||
edition = "2024"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -39,9 +39,8 @@ 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 = "1.2.1", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
slack = { version = "2.0.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
derive_default_builder = "0.1.8"
|
||||
derive_empty_traits = "0.1.0"
|
||||
async_timing_util = "1.1.0"
|
||||
@@ -55,11 +54,10 @@ svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.12.24", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
|
||||
tokio = { version = "1.47.1", features = ["full"] }
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.16", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["sync"] }
|
||||
pin-project-lite = "0.2.16"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3.31"
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
@@ -72,7 +70,7 @@ axum = { version = "0.8.6", features = ["ws", "json", "macros"] }
|
||||
|
||||
# SER/DE
|
||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||
indexmap = { version = "2.11.4", features = ["serde"] }
|
||||
indexmap = { version = "2.12.0", features = ["serde"] }
|
||||
serde = { version = "1.0.227", features = ["derive"] }
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
bson = { version = "2.15.0" } # must keep in sync with mongodb version
|
||||
@@ -96,14 +94,14 @@ opentelemetry = "0.31.0"
|
||||
tracing = "0.1.41"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.49", features = ["derive"] }
|
||||
clap = { version = "4.5.50", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
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.32", features = ["aws-lc-rs"] }
|
||||
jsonwebtoken = { version = "10.1.0", features = ["aws_lc_rs"] } # locked back with octorust
|
||||
rustls = { version = "0.23.34", features = ["aws-lc-rs"] }
|
||||
pem-rfc7468 = { version = "0.7.0", features = ["alloc"] }
|
||||
openidconnect = "4.0.1"
|
||||
urlencoding = "2.1.3"
|
||||
@@ -120,13 +118,17 @@ spki = "0.7.3"
|
||||
der = "0.7.10"
|
||||
|
||||
# SYSTEM
|
||||
hickory-resolver = "0.25.2"
|
||||
portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.19.3"
|
||||
sysinfo = "0.37.1"
|
||||
shlex = "1.3.0"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.8.8"
|
||||
aws-sdk-ec2 = "1.173.0"
|
||||
aws-sdk-ec2 = "1.176.0"
|
||||
aws-credential-types = "1.2.8"
|
||||
|
||||
## CRON
|
||||
@@ -138,11 +140,10 @@ croner = "3.0.0"
|
||||
# MISC
|
||||
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
|
||||
derive_builder = "0.20.2"
|
||||
shell-escape = "0.1.5"
|
||||
comfy-table = "7.2.1"
|
||||
typeshare = "1.0.4"
|
||||
dashmap = "6.1.0"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.0.0"
|
||||
bytes = "1.10.1"
|
||||
regex = "1.12.2"
|
||||
bytes = "1.10.1"
|
||||
@@ -0,0 +1,4 @@
|
||||
const cmd = "km run -y action deploy-komodo-fe-change";
|
||||
new Deno.Command("bash", {
|
||||
args: ["-c", cmd],
|
||||
}).spawn();
|
||||
@@ -23,7 +23,9 @@ noise.workspace = true
|
||||
# external
|
||||
futures-util.workspace = true
|
||||
comfy-table.workspace = true
|
||||
tokio-util.workspace = true
|
||||
serde_json.workspace = true
|
||||
crossterm.workspace = true
|
||||
serde_qs.workspace = true
|
||||
wildcard.workspace = true
|
||||
tracing.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()
|
||||
}),
|
||||
)?;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use komodo_client::{
|
||||
api::read::{
|
||||
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
||||
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
||||
ListSchedules, ListServers, ListStacks, ListTags,
|
||||
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
|
||||
},
|
||||
entities::{
|
||||
ResourceTargetVariant,
|
||||
@@ -35,6 +35,7 @@ use komodo_client::{
|
||||
ResourceSyncListItem, ResourceSyncListItemInfo,
|
||||
ResourceSyncState,
|
||||
},
|
||||
terminal::Terminal,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
@@ -74,15 +75,18 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
||||
Some(ListCommand::Syncs(filters)) => {
|
||||
list_resources::<ResourceSyncListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Terminals(filters)) => {
|
||||
list_terminals(filters).await
|
||||
}
|
||||
Some(ListCommand::Schedules(filters)) => {
|
||||
list_schedules(filters).await
|
||||
}
|
||||
Some(ListCommand::Builders(filters)) => {
|
||||
list_resources::<BuilderListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Alerters(filters)) => {
|
||||
list_resources::<AlerterListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Schedules(filters)) => {
|
||||
list_schedules(filters).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +193,26 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 terminals = client
|
||||
.read(ListTerminals {
|
||||
target: None,
|
||||
use_names: true,
|
||||
})
|
||||
.await?;
|
||||
if !terminals.is_empty() {
|
||||
print_items(terminals, filters.format, filters.links)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_schedules(
|
||||
filters: &ResourceFilters,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -1134,6 +1158,28 @@ impl PrintTable for ResourceListItem<AlerterListItemInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Terminal {
|
||||
fn header(_links: bool) -> &'static [&'static str] {
|
||||
&["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(format!("{:?}", self.target)),
|
||||
Cell::new(self.command),
|
||||
Cell::new(if self.stored_size_kb < 1.0 {
|
||||
format!("{:.1} KiB", self.stored_size_kb)
|
||||
} else {
|
||||
format!("{:.} KiB", self.stored_size_kb)
|
||||
}),
|
||||
Cell::new(
|
||||
format_timetamp(self.created_at)
|
||||
.unwrap_or_else(|_| String::from("Invalid created at")),
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Schedule {
|
||||
fn header(links: bool) -> &'static [&'static str] {
|
||||
if links {
|
||||
@@ -1146,7 +1192,7 @@ impl PrintTable for Schedule {
|
||||
let next_run = if let Some(ts) = self.next_scheduled_run {
|
||||
Cell::new(
|
||||
format_timetamp(ts)
|
||||
.unwrap_or(String::from("Invalid next ts")),
|
||||
.unwrap_or_else(|_| String::from("Invalid next ts")),
|
||||
)
|
||||
.add_attribute(Attribute::Bold)
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod container;
|
||||
pub mod database;
|
||||
pub mod execute;
|
||||
pub mod list;
|
||||
pub mod terminal;
|
||||
pub mod update;
|
||||
|
||||
async fn komodo_client() -> anyhow::Result<&'static KomodoClient> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+29
-8
@@ -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;
|
||||
@@ -41,12 +42,6 @@ async fn app() -> anyhow::Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
args::Command::Key { command } => {
|
||||
noise::key::command::handle(command).await
|
||||
}
|
||||
args::Command::Database { command } => {
|
||||
command::database::handle(command).await
|
||||
}
|
||||
args::Command::Container(container) => {
|
||||
command::container::handle(container).await
|
||||
}
|
||||
@@ -60,6 +55,21 @@ async fn app() -> anyhow::Result<()> {
|
||||
args::Command::Update { command } => {
|
||||
command::update::handle(command).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
|
||||
}
|
||||
args::Command::Database { command } => {
|
||||
command::database::handle(command).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,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(())
|
||||
}
|
||||
|
||||
+2
-1
@@ -45,6 +45,7 @@ aws-credential-types.workspace = true
|
||||
english-to-cron.workspace = true
|
||||
openidconnect.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
futures-util.workspace = true
|
||||
axum-server.workspace = true
|
||||
urlencoding.workspace = true
|
||||
aws-sdk-ec2.workspace = true
|
||||
@@ -54,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
|
||||
@@ -63,7 +65,6 @@ colored.workspace = true
|
||||
dashmap.workspace = true
|
||||
tracing.workspace = true
|
||||
reqwest.workspace = true
|
||||
futures.workspace = true
|
||||
dotenvy.workspace = true
|
||||
anyhow.workspace = true
|
||||
croner.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 .
|
||||
@@ -48,6 +48,9 @@ RUN mkdir /action-cache && \
|
||||
cd /action-cache && \
|
||||
deno install jsr:@std/yaml jsr:@std/toml
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9120
|
||||
|
||||
|
||||
@@ -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 .
|
||||
@@ -28,7 +28,7 @@ COPY --from=x86_64 /core /app/core/linux/amd64
|
||||
COPY --from=aarch64 /core /app/core/linux/arm64
|
||||
RUN mv /app/core/${TARGETPLATFORM} /usr/local/bin/core && rm -r /app/core
|
||||
|
||||
# Same for util
|
||||
# Same for km
|
||||
COPY --from=x86_64 /km /app/km/linux/amd64
|
||||
COPY --from=aarch64 /km /app/km/linux/arm64
|
||||
RUN mv /app/km/${TARGETPLATFORM} /usr/local/bin/km && rm -r /app/km
|
||||
@@ -44,6 +44,9 @@ RUN mkdir /action-cache && \
|
||||
cd /action-cache && \
|
||||
deno install jsr:@std/yaml jsr:@std/toml
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9120
|
||||
|
||||
@@ -51,7 +54,8 @@ ENV KOMODO_CLI_CONFIG_PATHS="/config"
|
||||
# This ensures any `komodo.cli.*` takes precedence over the Core `/config/*config.*`
|
||||
ENV KOMODO_CLI_CONFIG_KEYWORDS="*config.*,*komodo.cli*.*"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && core" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "core" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -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 .
|
||||
@@ -33,6 +33,9 @@ RUN mkdir /action-cache && \
|
||||
cd /action-cache && \
|
||||
deno install jsr:@std/yaml jsr:@std/toml
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9120
|
||||
|
||||
@@ -40,7 +43,8 @@ ENV KOMODO_CLI_CONFIG_PATHS="/config"
|
||||
# This ensures any `komodo.cli.*` takes precedence over the Core `/config/*config.*`
|
||||
ENV KOMODO_CLI_CONFIG_KEYWORDS="*config.*,*komodo.cli*.*"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && core" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "core" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use derive_variants::ExtractVariant;
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::entities::{
|
||||
ResourceTargetVariant,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use formatting::format_serror;
|
||||
use futures::{StreamExt, TryStreamExt, stream::FuturesUnordered};
|
||||
use futures_util::{
|
||||
StreamExt, TryStreamExt, stream::FuturesUnordered,
|
||||
};
|
||||
use komodo_client::{
|
||||
api::execute::{SendAlert, TestAlerter},
|
||||
entities::{
|
||||
@@ -102,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
|
||||
|
||||
@@ -14,7 +14,7 @@ use database::mungos::{
|
||||
},
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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,
|
||||
};
|
||||
use formatting::{bold, format_serror};
|
||||
use futures::{StreamExt, stream::FuturesOrdered};
|
||||
use futures_util::{StreamExt, stream::FuturesOrdered};
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
BackupCoreDatabase, ClearRepoCache, GlobalAutoUpdate,
|
||||
@@ -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::{
|
||||
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,
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -6,7 +6,7 @@ use database::mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOptions},
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use futures_util::TryStreamExt;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
@@ -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,
|
||||
@@ -50,6 +51,7 @@ mod server;
|
||||
mod stack;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod terminal;
|
||||
mod toml;
|
||||
mod update;
|
||||
mod user;
|
||||
@@ -113,6 +115,8 @@ enum ReadRequest {
|
||||
GetHistoricalServerStats(GetHistoricalServerStats),
|
||||
ListServers(ListServers),
|
||||
ListFullServers(ListFullServers),
|
||||
|
||||
// ==== TERMINAL ====
|
||||
ListTerminals(ListTerminals),
|
||||
|
||||
// ==== DOCKER ====
|
||||
@@ -397,16 +401,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
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
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,
|
||||
)
|
||||
)?;
|
||||
|
||||
@@ -25,11 +25,10 @@ use komodo_client::{
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
server::{
|
||||
Server, ServerActionState, ServerListItem, ServerState,
|
||||
TerminalInfo,
|
||||
Server, ServerActionState, ServerListItem, ServerQuery,
|
||||
ServerState,
|
||||
},
|
||||
stack::{Stack, StackServiceNames},
|
||||
stats::{SystemInformation, SystemProcess},
|
||||
@@ -50,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},
|
||||
@@ -66,6 +65,7 @@ impl Resolve<ReadArgs> for GetServersSummary {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
@@ -126,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?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -144,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?,
|
||||
)
|
||||
@@ -389,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();
|
||||
|
||||
@@ -407,9 +410,17 @@ 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(more) = &cache.containers else {
|
||||
continue;
|
||||
};
|
||||
let more = more
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
self.containers.is_empty()
|
||||
|| self.containers.contains(&container.name)
|
||||
})
|
||||
.cloned();
|
||||
containers.extend(more);
|
||||
}
|
||||
|
||||
Ok(containers)
|
||||
@@ -424,6 +435,7 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
@@ -575,12 +587,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 {
|
||||
@@ -821,68 +833,46 @@ impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
}
|
||||
}
|
||||
|
||||
#[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?;
|
||||
let cache = terminals_cache().get_or_insert(server.id.clone());
|
||||
let mut cache = cache.lock().await;
|
||||
if self.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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -46,6 +46,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 +57,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 +68,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 +79,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 +90,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 +101,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 +112,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 +123,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 +134,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 +145,7 @@ async fn get_all_targets(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
|
||||
+127
-89
@@ -29,7 +29,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 +45,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! {
|
||||
|
||||
+10
-179
@@ -1,26 +1,15 @@
|
||||
use anyhow::Context;
|
||||
use axum::{Extension, Router, middleware, routing::post};
|
||||
use komodo_client::{
|
||||
api::terminal::*,
|
||||
entities::{
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server, stack::Stack, user::User,
|
||||
},
|
||||
};
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -33,187 +22,29 @@ 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,
|
||||
}): Json<ExecuteTerminalBody>,
|
||||
) -> 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 stream = periphery_client(&server)
|
||||
.await?
|
||||
.execute_terminal(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 (target, terminal, periphery) =
|
||||
setup_target_for_user(target, terminal, init, &user).await?;
|
||||
|
||||
let stream = periphery
|
||||
.execute_container_exec(container, shell, command, recreate)
|
||||
.execute_terminal(target, terminal, command)
|
||||
.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)
|
||||
.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)
|
||||
.await
|
||||
.context(
|
||||
"Failed to execute container exec command on periphery",
|
||||
)?;
|
||||
.context("Failed to execute command on Terminal")?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(stream))
|
||||
}
|
||||
|
||||
@@ -390,23 +390,28 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
None
|
||||
};
|
||||
|
||||
let (
|
||||
remote_path,
|
||||
remote_contents,
|
||||
remote_error,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
) = if build.config.files_on_host {
|
||||
let RemoteDockerfileContents {
|
||||
path,
|
||||
contents,
|
||||
error,
|
||||
hash,
|
||||
message,
|
||||
} = if build.config.files_on_host {
|
||||
// =============
|
||||
// FILES ON HOST
|
||||
// =============
|
||||
match get_on_host_dockerfile(&build).await {
|
||||
Ok(FileContents { path, contents }) => {
|
||||
(Some(path), Some(contents), None, None, None)
|
||||
}
|
||||
Err(e) => {
|
||||
(None, None, Some(format_serror(&e.into())), None, None)
|
||||
RemoteDockerfileContents {
|
||||
path: Some(path),
|
||||
contents: Some(contents),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
Err(e) => RemoteDockerfileContents {
|
||||
error: Some(format_serror(&e.into())),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
} else if let Some(repo) = &repo {
|
||||
let Some(res) = get_git_remote(&build, repo.into()).await?
|
||||
@@ -426,7 +431,7 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
// =============
|
||||
// UI BASED FILE
|
||||
// =============
|
||||
(None, None, None, None, None)
|
||||
RemoteDockerfileContents::default()
|
||||
};
|
||||
|
||||
let info = BuildInfo {
|
||||
@@ -434,11 +439,11 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
built_hash: build.info.built_hash,
|
||||
built_message: build.info.built_message,
|
||||
built_contents: build.info.built_contents,
|
||||
remote_path,
|
||||
remote_contents,
|
||||
remote_error,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
remote_path: path,
|
||||
remote_contents: contents,
|
||||
remote_error: error,
|
||||
latest_hash: hash,
|
||||
latest_message: message,
|
||||
};
|
||||
|
||||
let info = to_document(&info)
|
||||
@@ -530,15 +535,7 @@ async fn get_on_host_dockerfile(
|
||||
async fn get_git_remote(
|
||||
build: &Build,
|
||||
mut clone_args: RepoExecutionArgs,
|
||||
) -> anyhow::Result<
|
||||
Option<(
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
)>,
|
||||
> {
|
||||
) -> anyhow::Result<Option<RemoteDockerfileContents>> {
|
||||
if clone_args.provider.is_empty() {
|
||||
// Nothing to do here
|
||||
return Ok(None);
|
||||
@@ -565,7 +562,17 @@ async fn get_git_remote(
|
||||
access_token,
|
||||
)
|
||||
.await
|
||||
.context("failed to clone build repo")?;
|
||||
.context("Failed to clone Build repo")?;
|
||||
|
||||
// Ensure clone / pull successful,
|
||||
// propogate error log -> 'errored' and return.
|
||||
if let Some(failure) = res.logs.iter().find(|log| !log.success) {
|
||||
return Ok(Some(RemoteDockerfileContents {
|
||||
path: Some(format!("Failed at: {}", failure.stage)),
|
||||
error: Some(failure.combined()),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
|
||||
let relative_path = PathBuf::from(&build.config.build_path)
|
||||
.join(&build.config.dockerfile_path);
|
||||
@@ -578,11 +585,20 @@ async fn get_git_remote(
|
||||
Ok(contents) => (Some(contents), None),
|
||||
Err(e) => (None, Some(format_serror(&e.into()))),
|
||||
};
|
||||
Ok(Some((
|
||||
Some(relative_path.display().to_string()),
|
||||
Ok(Some(RemoteDockerfileContents {
|
||||
path: Some(relative_path.display().to_string()),
|
||||
contents,
|
||||
error,
|
||||
res.commit_hash,
|
||||
res.commit_message,
|
||||
)))
|
||||
hash: res.commit_hash,
|
||||
message: res.commit_message,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RemoteDockerfileContents {
|
||||
pub path: Option<String>,
|
||||
pub contents: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub hash: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ mod service_user;
|
||||
mod stack;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod terminal;
|
||||
mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
@@ -89,9 +90,6 @@ pub enum WriteRequest {
|
||||
UpdateServer(UpdateServer),
|
||||
RenameServer(RenameServer),
|
||||
CreateNetwork(CreateNetwork),
|
||||
CreateTerminal(CreateTerminal),
|
||||
DeleteTerminal(DeleteTerminal),
|
||||
DeleteAllTerminals(DeleteAllTerminals),
|
||||
UpdateServerPublicKey(UpdateServerPublicKey),
|
||||
RotateServerKeys(RotateServerKeys),
|
||||
|
||||
@@ -167,6 +165,12 @@ pub enum WriteRequest {
|
||||
CommitSync(CommitSync),
|
||||
RefreshResourceSyncPending(RefreshResourceSyncPending),
|
||||
|
||||
// ==== TERMINAL ====
|
||||
CreateTerminal(CreateTerminal),
|
||||
DeleteTerminal(DeleteTerminal),
|
||||
DeleteAllTerminals(DeleteAllTerminals),
|
||||
BatchDeleteAllTerminals(BatchDeleteAllTerminals),
|
||||
|
||||
// ==== TAG ====
|
||||
CreateTag(CreateTag),
|
||||
DeleteTag(DeleteTag),
|
||||
|
||||
@@ -3,7 +3,7 @@ use formatting::{bold, format_serror};
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
NoData, Operation,
|
||||
Operation,
|
||||
permission::PermissionLevel,
|
||||
server::{Server, ServerInfo},
|
||||
to_docker_compatible_name,
|
||||
@@ -187,109 +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 UpdateServerPublicKey {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ use transport::{
|
||||
},
|
||||
channel::{BufferedReceiver, Sender, buffered_channel},
|
||||
websocket::{
|
||||
Websocket, WebsocketMessage, WebsocketReceiver as _,
|
||||
Websocket, WebsocketReceiver as _, WebsocketReceiverExt,
|
||||
WebsocketSender as _,
|
||||
},
|
||||
};
|
||||
@@ -257,7 +257,8 @@ impl<'a> From<&'a OwnedPeripheryConnectionArgs>
|
||||
pub type ResponseChannels =
|
||||
CloneCache<Uuid, Sender<EncodedResponse<EncodedJsonMessage>>>;
|
||||
|
||||
pub type TerminalChannels = CloneCache<Uuid, Sender<Vec<u8>>>;
|
||||
pub type TerminalChannels =
|
||||
CloneCache<Uuid, Sender<anyhow::Result<Vec<u8>>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PeripheryConnection {
|
||||
@@ -366,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(),
|
||||
@@ -384,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();
|
||||
@@ -409,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) {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod matcher;
|
||||
pub mod procedure;
|
||||
pub mod prune;
|
||||
pub mod query;
|
||||
pub mod terminal;
|
||||
pub mod update;
|
||||
|
||||
// pub mod resource;
|
||||
|
||||
@@ -3,13 +3,14 @@ use std::time::{Duration, Instant};
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mungos::by_id::find_one_by_id;
|
||||
use formatting::{Color, bold, colored, format_serror, muted};
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
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?
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_timing_util::{
|
||||
ONE_DAY_MS, Timelength, unix_timestamp_ms, wait_until_timelength,
|
||||
};
|
||||
use database::mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use futures_util::{StreamExt, stream::FuturesUnordered};
|
||||
use periphery_client::api::docker::PruneImages;
|
||||
|
||||
use crate::{config::core_config, state::db_client};
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock};
|
||||
use async_timing_util::wait_until_timelength;
|
||||
use cache::CloneCache;
|
||||
use database::mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use helpers::insert_stacks_status_unknown;
|
||||
use komodo_client::entities::{
|
||||
build::Build,
|
||||
|
||||
@@ -21,6 +21,7 @@ pub mod terminal;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PeripheryClient {
|
||||
/// Usually the server id
|
||||
pub id: String,
|
||||
pub responses: Arc<ResponseChannels>,
|
||||
pub terminals: Arc<TerminalChannels>,
|
||||
|
||||
@@ -5,117 +5,37 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use cache::CloneCache;
|
||||
use futures::Stream;
|
||||
use komodo_client::api::write::TerminalRecreateMode;
|
||||
use futures_util::Stream;
|
||||
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, channel};
|
||||
use transport::channel::{Receiver, Sender};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
periphery::PeripheryClient, state::periphery_connections,
|
||||
};
|
||||
|
||||
pub struct ConnectTerminalResponse {
|
||||
pub channel: Uuid,
|
||||
pub sender: Sender<EncodedTransportMessage>,
|
||||
pub receiver: Receiver<anyhow::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl PeripheryClient {
|
||||
#[instrument("ConnectTerminal", skip(self), fields(server_id = self.id))]
|
||||
pub async fn connect_terminal(
|
||||
&self,
|
||||
terminal: String,
|
||||
) -> anyhow::Result<(
|
||||
Uuid,
|
||||
Sender<EncodedTransportMessage>,
|
||||
Receiver<Vec<u8>>,
|
||||
)> {
|
||||
target: TerminalTarget,
|
||||
) -> anyhow::Result<ConnectTerminalResponse> {
|
||||
tracing::trace!(
|
||||
"request | type: ConnectTerminal | terminal name: {terminal}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
periphery_connections().get(&self.id).await.with_context(
|
||||
|| format!("No connection found for server {}", self.id),
|
||||
)?;
|
||||
|
||||
let channel_id = self
|
||||
.request(ConnectTerminal { terminal })
|
||||
.await
|
||||
.context("Failed to create terminal connection")?;
|
||||
|
||||
let (sender, receiever) = channel();
|
||||
connection.terminals.insert(channel_id, sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel_id, Bytes::new())
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok((channel_id, connection.sender.clone(), receiever))
|
||||
}
|
||||
|
||||
#[instrument("ConnectContainerExec", skip(self), fields(server_id = self.id))]
|
||||
pub async fn connect_container_exec(
|
||||
&self,
|
||||
container: String,
|
||||
shell: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) -> anyhow::Result<(
|
||||
Uuid,
|
||||
Sender<EncodedTransportMessage>,
|
||||
Receiver<Vec<u8>>,
|
||||
)> {
|
||||
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_id = self
|
||||
.request(ConnectContainerExec {
|
||||
container,
|
||||
shell,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create container exec connection")?;
|
||||
|
||||
let (sender, receiever) = channel();
|
||||
connection.terminals.insert(channel_id, sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel_id, Bytes::new())
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok((channel_id, connection.sender.clone(), receiever))
|
||||
}
|
||||
|
||||
#[instrument("ConnectContainerAttach", skip(self), fields(server_id = self.id))]
|
||||
pub async fn connect_container_attach(
|
||||
&self,
|
||||
container: String,
|
||||
recreate: TerminalRecreateMode,
|
||||
) -> anyhow::Result<(
|
||||
Uuid,
|
||||
Sender<EncodedTransportMessage>,
|
||||
Receiver<Vec<u8>>,
|
||||
)> {
|
||||
tracing::trace!(
|
||||
"request | type: ConnectContainerAttach | container name: {container}",
|
||||
"request | type: ConnectTerminal | Terminal: {terminal} | Target: {target:?}",
|
||||
);
|
||||
|
||||
let connection =
|
||||
@@ -124,25 +44,29 @@ impl PeripheryClient {
|
||||
)?;
|
||||
|
||||
let channel = self
|
||||
.request(ConnectContainerAttach {
|
||||
container,
|
||||
recreate,
|
||||
})
|
||||
.request(ConnectTerminal { terminal, target })
|
||||
.await
|
||||
.context("Failed to create container attach connection")?;
|
||||
.context("Failed to create terminal connection")?;
|
||||
|
||||
let (sender, receiever) = transport::channel::channel();
|
||||
let (sender, receiver) = transport::channel::channel();
|
||||
connection.terminals.insert(channel, sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel, Bytes::new())
|
||||
.send_terminal(
|
||||
channel,
|
||||
Ok(vec![TerminalStdinMessageVariant::Begin.as_byte()]),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
"Failed to send TerminalMessage Begin byte to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok((channel, connection.sender.clone(), receiever))
|
||||
Ok(ConnectTerminalResponse {
|
||||
channel,
|
||||
sender: connection.sender.clone(),
|
||||
receiver,
|
||||
})
|
||||
}
|
||||
|
||||
/// Executes command on specified terminal,
|
||||
@@ -161,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 =
|
||||
@@ -175,87 +100,32 @@ impl PeripheryClient {
|
||||
|| format!("No connection found for server {}", self.id),
|
||||
)?;
|
||||
|
||||
let channel_id = self
|
||||
.request(ExecuteTerminal { terminal, command })
|
||||
let channel = self
|
||||
.request(ExecuteTerminal {
|
||||
terminal,
|
||||
target,
|
||||
command,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create execute terminal connection")?;
|
||||
|
||||
let (terminal_sender, terminal_receiver) = channel();
|
||||
connection
|
||||
.terminals
|
||||
.insert(channel_id, terminal_sender)
|
||||
.await;
|
||||
let (terminal_sender, terminal_receiver) =
|
||||
transport::channel::channel();
|
||||
connection.terminals.insert(channel, terminal_sender).await;
|
||||
|
||||
connection
|
||||
.sender
|
||||
.send_terminal(channel_id, Bytes::new())
|
||||
.send_terminal(
|
||||
channel,
|
||||
Ok(vec![TerminalStdinMessageVariant::Begin.as_byte()]),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to send TerminalTrigger to begin forwarding.",
|
||||
)?;
|
||||
|
||||
Ok(ReceiverStream {
|
||||
channel_id,
|
||||
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_id = self
|
||||
.request(ExecuteContainerExec {
|
||||
container,
|
||||
shell,
|
||||
command,
|
||||
recreate,
|
||||
})
|
||||
.await
|
||||
.context("Failed to create execute terminal connection")?;
|
||||
|
||||
let (terminal_sender, terminal_receiver) = channel();
|
||||
connection
|
||||
.terminals
|
||||
.insert(channel_id, 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_id, Bytes::new())
|
||||
.await?;
|
||||
|
||||
Ok(ReceiverStream {
|
||||
channel_id,
|
||||
channel,
|
||||
receiver: terminal_receiver,
|
||||
channels: connection.terminals.clone(),
|
||||
})
|
||||
@@ -263,9 +133,9 @@ impl PeripheryClient {
|
||||
}
|
||||
|
||||
pub struct ReceiverStream {
|
||||
channel_id: Uuid,
|
||||
channels: Arc<CloneCache<Uuid, Sender<Vec<u8>>>>,
|
||||
receiver: Receiver<Vec<u8>>,
|
||||
channel: Uuid,
|
||||
channels: Arc<CloneCache<Uuid, Sender<anyhow::Result<Vec<u8>>>>>,
|
||||
receiver: Receiver<anyhow::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Stream for ReceiverStream {
|
||||
@@ -275,14 +145,14 @@ impl Stream for ReceiverStream {
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
match self.receiver.poll_recv(cx) {
|
||||
Poll::Ready(Some(bytes))
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
if bytes == END_OF_OUTPUT.as_bytes() =>
|
||||
{
|
||||
self.cleanup();
|
||||
Poll::Ready(None)
|
||||
}
|
||||
Poll::Ready(Some(bytes)) => Poll::Ready(Some(Ok(bytes))),
|
||||
// Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||
Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))),
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||
Poll::Ready(None) => {
|
||||
self.cleanup();
|
||||
Poll::Ready(None)
|
||||
@@ -296,9 +166,9 @@ impl ReceiverStream {
|
||||
fn cleanup(&self) {
|
||||
// Not the prettiest but it should be fine
|
||||
let channels = self.channels.clone();
|
||||
let id = self.channel_id;
|
||||
let channel = self.channel;
|
||||
tokio::spawn(async move {
|
||||
channels.remove(&id).await;
|
||||
channels.remove(&channel).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+160
-39
@@ -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 futures::{FutureExt, future::BoxFuture};
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -14,14 +14,17 @@ use database::mungos::{
|
||||
},
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::{
|
||||
api::{read::ExportResourcesToToml, write::CreateTag},
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -251,30 +254,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 +304,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 +325,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 +366,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 +464,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)
|
||||
|
||||
@@ -5,7 +5,7 @@ use database::mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{Collection, bson::doc, options::FindOneOptions},
|
||||
};
|
||||
use futures::{TryStreamExt, stream::FuturesUnordered};
|
||||
use futures_util::{TryStreamExt, stream::FuturesUnordered};
|
||||
use komodo_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
|
||||
@@ -71,6 +71,7 @@ impl super::KomodoResource for Server {
|
||||
let (
|
||||
version,
|
||||
public_key,
|
||||
public_ip,
|
||||
terminals_disabled,
|
||||
container_terminals_disabled,
|
||||
) = match status.as_ref().and_then(|s| s.periphery_info.as_ref())
|
||||
@@ -78,10 +79,11 @@ impl super::KomodoResource for Server {
|
||||
Some(info) => (
|
||||
Some(info.version.clone()),
|
||||
Some(info.public_key.clone()),
|
||||
info.public_ip.clone(),
|
||||
info.terminals_disabled,
|
||||
info.container_terminals_disabled,
|
||||
),
|
||||
None => (None, None, true, true),
|
||||
None => (None, None, None, true, true),
|
||||
};
|
||||
ServerListItem {
|
||||
name: server.name,
|
||||
@@ -110,6 +112,7 @@ impl super::KomodoResource for Server {
|
||||
attempted_public_key: optional_string(
|
||||
server.info.attempted_public_key,
|
||||
),
|
||||
public_ip,
|
||||
terminals_disabled,
|
||||
container_terminals_disabled,
|
||||
},
|
||||
|
||||
@@ -11,12 +11,13 @@ use komodo_client::entities::{
|
||||
|
||||
use crate::{config::core_config, helpers::git_token};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RemoteComposeContents {
|
||||
pub successful: Vec<StackRemoteFileContents>,
|
||||
pub errored: Vec<FileContents>,
|
||||
pub hash: Option<String>,
|
||||
pub message: Option<String>,
|
||||
pub _logs: Vec<Log>,
|
||||
// pub logs: Vec<Log>,
|
||||
}
|
||||
|
||||
/// Returns Result<(read paths, error paths, logs, short hash, commit message)>
|
||||
@@ -28,11 +29,23 @@ pub async fn get_repo_compose_contents(
|
||||
) -> anyhow::Result<RemoteComposeContents> {
|
||||
let clone_args: RepoExecutionArgs =
|
||||
repo.map(Into::into).unwrap_or(stack.into());
|
||||
let (repo_path, _logs, hash, message) =
|
||||
let (repo_path, logs, hash, message) =
|
||||
ensure_remote_repo(clone_args)
|
||||
.await
|
||||
.context("Failed to clone stack repo")?;
|
||||
|
||||
// Ensure clone / pull successful,
|
||||
// propogate error log -> 'errored' and return.
|
||||
if let Some(failure) = logs.iter().find(|log| !log.success) {
|
||||
return Ok(RemoteComposeContents {
|
||||
errored: vec![FileContents {
|
||||
path: format!("Failed at: {}", failure.stage),
|
||||
contents: failure.combined(),
|
||||
}],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let run_directory = repo_path.join(&stack.config.run_directory);
|
||||
// This will remove any intermediate '/./' which can be a problem for some OS.
|
||||
let run_directory = run_directory.components().collect::<PathBuf>();
|
||||
@@ -69,7 +82,6 @@ pub async fn get_repo_compose_contents(
|
||||
errored,
|
||||
hash,
|
||||
message,
|
||||
_logs,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use database::mungos::{
|
||||
Document, doc, oid::ObjectId, to_bson, to_document,
|
||||
},
|
||||
};
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
auth::SignUpLocalUser,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use formatting::{Color, bold, colored, format_serror, muted};
|
||||
use futures::future::join_all;
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
execute::{Deploy, DeployStack},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::{Context, anyhow};
|
||||
use komodo_client::entities::{
|
||||
RepoExecutionArgs, RepoExecutionResponse,
|
||||
repo::Repo,
|
||||
@@ -99,9 +99,22 @@ async fn get_repo(
|
||||
format!("Failed to update resource repo at {repo_path:?}")
|
||||
})?;
|
||||
|
||||
// let hash = hash.context("failed to get commit hash")?;
|
||||
// let message =
|
||||
// message.context("failed to get commit hash message")?;
|
||||
// Ensure clone / pull successful,
|
||||
// propogate error log -> 'errored' and return.
|
||||
if let Some(failure) = logs.iter().find(|log| !log.success) {
|
||||
return Ok(RemoteResources {
|
||||
resources: Err(anyhow!("Repo clone / pull failed")),
|
||||
files: Vec::new(),
|
||||
file_errors: vec![SyncFileContents {
|
||||
resource_path: String::from("Repo error"),
|
||||
path: format!("Failed at: {}", failure.stage),
|
||||
contents: failure.combined(),
|
||||
}],
|
||||
logs,
|
||||
hash: None,
|
||||
message: None,
|
||||
});
|
||||
}
|
||||
|
||||
let (mut files, mut file_errors) = (Vec::new(), Vec::new());
|
||||
let resources = super::file::read_resources(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures::SinkExt;
|
||||
use komodo_client::{
|
||||
api::terminal::{
|
||||
ConnectContainerAttachQuery, ConnectContainerExecQuery,
|
||||
},
|
||||
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,
|
||||
)
|
||||
.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,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures::SinkExt;
|
||||
use komodo_client::{
|
||||
api::terminal::{
|
||||
ConnectDeploymentAttachQuery, ConnectDeploymentExecQuery,
|
||||
},
|
||||
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,
|
||||
)
|
||||
.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,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
+2
-235
@@ -1,8 +1,6 @@
|
||||
use crate::{
|
||||
auth::{auth_api_key_check_enabled, auth_jwt_check_enabled},
|
||||
helpers::query::get_user,
|
||||
periphery::PeripheryClient,
|
||||
state::periphery_connections,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
@@ -10,24 +8,9 @@ use axum::{
|
||||
extract::ws::{self, WebSocket},
|
||||
routing::get,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use komodo_client::{
|
||||
api::write::TerminalRecreateMode,
|
||||
entities::{server::Server, user::User},
|
||||
ws::WsLoginMessage,
|
||||
};
|
||||
use periphery_client::{
|
||||
api::terminal::DisconnectTerminal,
|
||||
transport::EncodedTransportMessage,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use transport::channel::{Receiver, Sender};
|
||||
use uuid::Uuid;
|
||||
use futures_util::SinkExt;
|
||||
use komodo_client::{entities::user::User, ws::WsLoginMessage};
|
||||
|
||||
mod container;
|
||||
mod deployment;
|
||||
mod stack;
|
||||
mod terminal;
|
||||
mod update;
|
||||
|
||||
@@ -38,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(
|
||||
@@ -134,213 +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 (periphery_connection_id, periphery_sender, periphery_receiver) =
|
||||
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,
|
||||
periphery_connection_id,
|
||||
periphery_sender,
|
||||
periphery_receiver,
|
||||
)
|
||||
.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 (periphery_connection_id, periphery_sender, periphery_receiver) =
|
||||
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,
|
||||
periphery_connection_id,
|
||||
periphery_sender,
|
||||
periphery_receiver,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn forward_ws_channel(
|
||||
periphery: PeripheryClient,
|
||||
client_socket: axum::extract::ws::WebSocket,
|
||||
periphery_connection_id: Uuid,
|
||||
periphery_sender: Sender<EncodedTransportMessage>,
|
||||
mut periphery_receiver: Receiver<Vec<u8>>,
|
||||
) {
|
||||
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(periphery_connection_id, bytes)
|
||||
.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(periphery_connection_id, bytes)
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send terminal message | {e:?}",);
|
||||
cancel.cancel();
|
||||
break;
|
||||
};
|
||||
}
|
||||
// TODO: Disconnect from periphery when client disconnects
|
||||
Some(Ok(ws::Message::Close(_frame))) => {
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
// Ignore
|
||||
Some(Ok(_)) => {}
|
||||
Some(Err(_e)) => {
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let periphery_to_core = async {
|
||||
loop {
|
||||
// Already adheres to cancellation token
|
||||
match periphery_receiver.recv().await {
|
||||
Ok(bytes) => {
|
||||
if let Err(e) =
|
||||
client_send.send(ws::Message::Binary(bytes.into())).await
|
||||
{
|
||||
debug!("{e:?}");
|
||||
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 {
|
||||
id: periphery_connection_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Failed to disconnect Periphery terminal forwarding | {e:#}",
|
||||
)
|
||||
}
|
||||
if let Some(connection) =
|
||||
periphery_connections().get(&periphery.id).await
|
||||
{
|
||||
connection.terminals.remove(&periphery_connection_id).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures::SinkExt;
|
||||
use komodo_client::{
|
||||
api::terminal::{ConnectStackAttachQuery, ConnectStackExecQuery},
|
||||
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,
|
||||
)
|
||||
.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,
|
||||
)
|
||||
.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))
|
||||
}
|
||||
+160
-66
@@ -1,23 +1,27 @@
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
extract::{Query, WebSocketUpgrade, ws::Message},
|
||||
extract::{FromRequestParts, WebSocketUpgrade, ws},
|
||||
http::request,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures::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,63 +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, 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;
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
periphery_connection_id,
|
||||
periphery_sender,
|
||||
periphery_receiver,
|
||||
) = 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");
|
||||
|
||||
forward_ws_channel(
|
||||
periphery,
|
||||
client_socket,
|
||||
periphery_connection_id,
|
||||
periphery_sender,
|
||||
periphery_receiver,
|
||||
)
|
||||
.await
|
||||
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}"),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use axum::{
|
||||
extract::{WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use komodo_client::entities::{
|
||||
ResourceTarget, permission::PermissionLevel, user::User,
|
||||
};
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
## Update certificates.
|
||||
update-ca-certificates
|
||||
|
||||
## Let the actual command take over
|
||||
exec "$@"
|
||||
@@ -35,9 +35,10 @@ 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
|
||||
futures-util.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
portable-pty.workspace = true
|
||||
shell-escape.workspace = true
|
||||
@@ -47,16 +48,16 @@ serde_json.workspace = true
|
||||
tokio-util.workspace = true
|
||||
arc-swap.workspace = true
|
||||
colored.workspace = true
|
||||
futures.workspace = true
|
||||
tracing.workspace = true
|
||||
bollard.workspace = true
|
||||
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 .
|
||||
@@ -22,6 +22,9 @@ RUN sh ./debian-deps.sh && rm ./debian-deps.sh
|
||||
|
||||
COPY --from=builder /builder/target/release/periphery /usr/local/bin/periphery
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
EXPOSE 8120
|
||||
|
||||
# Can mount config file to /config/*config*.toml and it will be picked up.
|
||||
@@ -29,7 +32,8 @@ ENV PERIPHERY_CONFIG_PATHS="/config"
|
||||
# Change the default in container to /config/keys to match Core
|
||||
ENV PERIPHERY_PRIVATE_KEY="file:/config/keys/periphery.key"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && periphery" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "periphery" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -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 .
|
||||
@@ -25,6 +25,9 @@ COPY --from=aarch64 /periphery /app/arch/linux/arm64
|
||||
ARG TARGETPLATFORM
|
||||
RUN mv /app/arch/${TARGETPLATFORM} /usr/local/bin/periphery && rm -r /app/arch
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
EXPOSE 8120
|
||||
|
||||
# Can mount config file to /config/*config*.toml and it will be picked up.
|
||||
@@ -32,7 +35,8 @@ ENV PERIPHERY_CONFIG_PATHS="/config"
|
||||
# Change the default in container to /config/keys to match Core
|
||||
ENV PERIPHERY_PRIVATE_KEY="file:/config/keys/periphery.key"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && periphery" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "periphery" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -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 .
|
||||
@@ -14,6 +14,9 @@ RUN sh ./debian-deps.sh && rm ./debian-deps.sh
|
||||
|
||||
COPY --from=binaries /periphery /usr/local/bin/periphery
|
||||
|
||||
COPY ./bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
EXPOSE 8120
|
||||
|
||||
# Can mount config file to /config/*config*.toml and it will be picked up.
|
||||
@@ -21,7 +24,8 @@ ENV PERIPHERY_CONFIG_PATHS="/config"
|
||||
# Change the default in container to /config/keys to match Core
|
||||
ENV PERIPHERY_PRIVATE_KEY="file:/config/keys/periphery.key"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && periphery" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "periphery" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -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,7 +4,7 @@ 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,
|
||||
@@ -99,7 +99,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,7 +2,8 @@ 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;
|
||||
@@ -43,7 +44,7 @@ fn docker_compose() -> &'static str {
|
||||
pub async fn list_compose_projects()
|
||||
-> anyhow::Result<Vec<ComposeProject>> {
|
||||
let docker_compose = docker_compose();
|
||||
let res = run_komodo_command(
|
||||
let res = run_komodo_standard_command(
|
||||
"List Projects",
|
||||
None,
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
@@ -111,7 +112,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 +140,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("Get stack log grep", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +402,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 +499,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 +543,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Compose Config",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Standard,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -603,7 +610,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Compose Build",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -625,7 +632,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,
|
||||
@@ -660,7 +667,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
"Compose Up",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
@@ -680,7 +687,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 +717,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 +811,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 +874,7 @@ impl Resolve<super::Args> for ComposeRun {
|
||||
"Compose Run",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
false,
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command;
|
||||
use futures::future::join_all;
|
||||
use command::{
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::entities::{
|
||||
docker::{
|
||||
container::{Container, ContainerListItem, ContainerStats},
|
||||
@@ -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,7 +1,7 @@
|
||||
use command::run_komodo_command;
|
||||
use command::run_komodo_standard_command;
|
||||
use derive_variants::EnumVariants;
|
||||
use encoding::{EncodedJsonMessage, EncodedResponse};
|
||||
use futures::FutureExt;
|
||||
use futures_util::FutureExt;
|
||||
use komodo_client::entities::{
|
||||
config::{DockerRegistry, GitProvider},
|
||||
server::PeripheryInformation,
|
||||
@@ -19,7 +19,9 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
api::compose::list_compose_projects,
|
||||
config::periphery_config,
|
||||
state::{docker_client, periphery_keys, stats_client},
|
||||
state::{
|
||||
docker_client, host_public_ip, periphery_keys, stats_client,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod terminal;
|
||||
@@ -142,15 +144,14 @@ pub enum PeripheryRequest {
|
||||
|
||||
// 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),
|
||||
@@ -240,6 +241,7 @@ impl Resolve<Args> for PollStatus {
|
||||
.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,
|
||||
@@ -308,6 +310,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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+136
-241
@@ -1,10 +1,10 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use futures::{Stream, StreamExt, TryStreamExt};
|
||||
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,
|
||||
@@ -29,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(),
|
||||
@@ -55,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 {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,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 {})
|
||||
}
|
||||
}
|
||||
@@ -118,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),
|
||||
@@ -131,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(),
|
||||
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(),
|
||||
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;
|
||||
@@ -257,17 +241,14 @@ impl Resolve<super::Args> for DisconnectTerminal {
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
channel_id = self.id.to_string(),
|
||||
channel_id = self.channel.to_string(),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
args: &super::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
if let Some(channel) = terminal_channels().remove(&self.id).await
|
||||
{
|
||||
channel.cancel.cancel();
|
||||
}
|
||||
terminal_channels().remove(&self.channel).await;
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
@@ -286,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();
|
||||
|
||||
@@ -321,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(),
|
||||
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();
|
||||
@@ -428,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.
|
||||
@@ -445,13 +348,13 @@ async fn handle_terminal_forwarding(
|
||||
let (a, b) = terminal.history.bytes_parts();
|
||||
if !a.is_empty() {
|
||||
sender
|
||||
.send_terminal(channel, a)
|
||||
.send_terminal(channel, Ok(a.into()))
|
||||
.await
|
||||
.context("Failed to send history part a")?;
|
||||
}
|
||||
if !b.is_empty() {
|
||||
sender
|
||||
.send_terminal(channel, b)
|
||||
.send_terminal(channel, Ok(b.into()))
|
||||
.await
|
||||
.context("Failed to send history part b")?;
|
||||
}
|
||||
@@ -468,36 +371,30 @@ 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() => {
|
||||
trace!("ws write: cancelled from outside");
|
||||
// let _ = ws_sender.send("PTY KILLED")).await;
|
||||
// if let Err(e) = ws_write.close().await {
|
||||
// debug!("Failed to close ws: {e:?}");
|
||||
// };
|
||||
let _ = sender.send_terminal_exited(channel).await;
|
||||
break
|
||||
},
|
||||
_ = cancel.cancelled() => {
|
||||
// let _ = ws_write.send(Message::Text(Utf8Bytes::from_static("WS KILLED"))).await;
|
||||
// if let Err(e) = ws_write.close().await {
|
||||
// debug!("Failed to close ws: {e:?}");
|
||||
// };
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
let bytes = match res {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
debug!("Terminal receiver failed | {e:?}");
|
||||
terminal.cancel();
|
||||
Err(_e) => {
|
||||
let _ = sender.send_terminal_exited(channel).await;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = sender.send_terminal(channel, bytes).await {
|
||||
if let Err(e) =
|
||||
sender.send_terminal(channel, Ok(bytes.into())).await
|
||||
{
|
||||
debug!("Failed to send to WS: {e:?}");
|
||||
cancel.cancel();
|
||||
break;
|
||||
@@ -505,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;
|
||||
}
|
||||
|
||||
@@ -518,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,
|
||||
@@ -541,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")?;
|
||||
|
||||
@@ -573,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:#}");
|
||||
@@ -583,14 +475,17 @@ async fn forward_execute_command_on_terminal_response(
|
||||
loop {
|
||||
match stdout.next().await {
|
||||
Some(Ok(line)) if line.as_str() == END_OF_OUTPUT => {
|
||||
if let Err(e) = sender.send_terminal(channel, line).await {
|
||||
if let Err(e) =
|
||||
sender.send_terminal(channel, Ok(line.into())).await
|
||||
{
|
||||
warn!("Got ws_sender send error on END_OF_OUTPUT | {e:?}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
Some(Ok(line)) => {
|
||||
if let Err(e) =
|
||||
sender.send_terminal(channel, line + "\n").await
|
||||
if let Err(e) = sender
|
||||
.send_terminal(channel, Ok((line + "\n").into()))
|
||||
.await
|
||||
{
|
||||
warn!("Got ws_sender send error | {e:?}");
|
||||
break;
|
||||
|
||||
@@ -108,6 +108,9 @@ pub fn periphery_config() -> &'static PeripheryConfig {
|
||||
repo_dir: env.periphery_repo_dir.or(config.repo_dir),
|
||||
stack_dir: env.periphery_stack_dir.or(config.stack_dir),
|
||||
build_dir: env.periphery_build_dir.or(config.build_dir),
|
||||
default_terminal_command: env
|
||||
.periphery_default_terminal_command
|
||||
.unwrap_or(config.default_terminal_command),
|
||||
disable_terminals: env
|
||||
.periphery_disable_terminals
|
||||
.unwrap_or(config.disable_terminals),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use bollard::Docker;
|
||||
use command::run_komodo_command;
|
||||
use command::{run_komodo_standard_command, run_shell_command};
|
||||
use komodo_client::entities::{TerminationSignal, update::Log};
|
||||
use run_command::async_run_command;
|
||||
|
||||
pub mod stats;
|
||||
|
||||
@@ -38,9 +37,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 +62,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(
|
||||
|
||||
@@ -3,7 +3,8 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use anyhow::{Context, anyhow};
|
||||
use async_timing_util::wait_until_timelength;
|
||||
use bollard::{models, query_parameters::StatsOptionsBuilder};
|
||||
use futures::StreamExt;
|
||||
use command::run_standard_command;
|
||||
use futures_util::StreamExt;
|
||||
use komodo_client::entities::docker::{
|
||||
container::ContainerStats,
|
||||
stats::{
|
||||
@@ -13,7 +14,6 @@ use komodo_client::entities::docker::{
|
||||
ContainerThrottlingData, FullContainerStats,
|
||||
},
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config, docker::DockerClient,
|
||||
@@ -65,7 +65,7 @@ pub async fn get_container_stats(
|
||||
};
|
||||
let command =
|
||||
format!("docker stats{container_name} --no-stream {format}");
|
||||
let output = async_run_command(&command).await;
|
||||
let output = run_standard_command(&command, None).await;
|
||||
if output.success() {
|
||||
output
|
||||
.stdout
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
net::IpAddr, path::PathBuf, str::FromStr as _, sync::OnceLock,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command_with_sanitization;
|
||||
use command::{
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
run_standard_command,
|
||||
};
|
||||
use environment::write_env_file;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
@@ -121,7 +126,7 @@ pub async fn handle_post_repo_execution(
|
||||
"On Clone",
|
||||
path.as_path(),
|
||||
on_clone.command,
|
||||
true,
|
||||
KomodoCommandMode::Multiline,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
@@ -146,7 +151,7 @@ pub async fn handle_post_repo_execution(
|
||||
"On Pull",
|
||||
path.as_path(),
|
||||
on_pull.command,
|
||||
true,
|
||||
KomodoCommandMode::Multiline,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
@@ -204,6 +209,57 @@ pub fn registry_token(
|
||||
.with_context(|| format!("did not find token in config for docker registry account {account_username} | domain {domain}"))
|
||||
}
|
||||
|
||||
// ====================
|
||||
// Public IP over DNS
|
||||
// ====================
|
||||
|
||||
type OpenDNSResolver = hickory_resolver::Resolver<
|
||||
hickory_resolver::name_server::TokioConnectionProvider,
|
||||
>;
|
||||
|
||||
fn opendns_resolver() -> &'static OpenDNSResolver {
|
||||
static OPENDNS_RESOLVER: OnceLock<OpenDNSResolver> =
|
||||
OnceLock::new();
|
||||
OPENDNS_RESOLVER.get_or_init(|| {
|
||||
// OpenDNS resolver ips
|
||||
let ips = [
|
||||
IpAddr::from_str("208.67.222.222").unwrap(),
|
||||
IpAddr::from_str("208.67.220.220").unwrap(),
|
||||
IpAddr::from_str("2620:119:35::35").unwrap(),
|
||||
IpAddr::from_str("2620:119:53::53").unwrap(),
|
||||
];
|
||||
|
||||
// trust_negative_responses=true means NXDOMAIN/empty NOERROR from an
|
||||
// authoritative upstream won’t be retried on other servers.
|
||||
let ns =
|
||||
hickory_resolver::config::NameServerConfigGroup::from_ips_clear(
|
||||
&ips, 53, true,
|
||||
);
|
||||
|
||||
hickory_resolver::Resolver::builder_with_config(
|
||||
hickory_resolver::config::ResolverConfig::from_parts(
|
||||
None,
|
||||
vec![],
|
||||
ns,
|
||||
),
|
||||
hickory_resolver::name_server::TokioConnectionProvider::default(
|
||||
),
|
||||
)
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn resolve_host_public_ip() -> anyhow::Result<String> {
|
||||
opendns_resolver()
|
||||
.lookup_ip("myip.opendns.com.")
|
||||
.await
|
||||
.context("Failed to query OpenDNS resolvers for host public IP")?
|
||||
.into_iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.next()
|
||||
.context("OpenDNS call for public IP didn't return anything")
|
||||
}
|
||||
|
||||
// =====
|
||||
// SSL
|
||||
// =====
|
||||
@@ -240,7 +296,7 @@ async fn generate_self_signed_ssl_certs() {
|
||||
let command = format!(
|
||||
"openssl req -x509 -newkey rsa:4096 -keyout {key_path} -out {cert_path} -sha256 -days 3650 -nodes -subj \"/C=XX/CN=periphery\""
|
||||
);
|
||||
let log = run_command::async_run_command(&command).await;
|
||||
let log = run_standard_command(&command, None).await;
|
||||
|
||||
if log.success() {
|
||||
info!("✅ SSL Certs generated");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use futures_util::{StreamExt, stream::FuturesUnordered};
|
||||
use komodo_client::entities::config::periphery::Command;
|
||||
use tracing::Instrument;
|
||||
|
||||
@@ -20,6 +20,7 @@ mod stats;
|
||||
mod terminal;
|
||||
|
||||
async fn app() -> anyhow::Result<()> {
|
||||
dotenvy::dotenv().ok();
|
||||
let config = config::periphery_config();
|
||||
logger::init(&config.logging)?;
|
||||
|
||||
@@ -102,21 +103,15 @@ async fn main() -> anyhow::Result<()> {
|
||||
return noise::key::command::handle(command).await;
|
||||
}
|
||||
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
res = tokio::spawn(app()) => return res?,
|
||||
_ = term_signal.recv() => {
|
||||
info!("Exiting all active Terminals for shutdown");
|
||||
terminal::delete_all_terminals().await;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+53
-15
@@ -6,20 +6,21 @@ use std::{
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use arc_swap::ArcSwap;
|
||||
use cache::CloneCache;
|
||||
use komodo_client::entities::docker::container::ContainerStats;
|
||||
use cache::{CloneCache, CloneVecCache};
|
||||
use komodo_client::entities::{
|
||||
docker::container::ContainerStats, terminal::TerminalStdinMessage,
|
||||
};
|
||||
use noise::key::{RotatableKeyPair, SpkiPublicKey};
|
||||
use periphery_client::transport::EncodedTransportMessage;
|
||||
use tokio::sync::{Mutex, RwLock, mpsc, oneshot};
|
||||
use tokio::sync::{Mutex, OnceCell, RwLock, mpsc, oneshot};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use transport::channel::BufferedChannel;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
docker::DockerClient,
|
||||
stats::StatsClient,
|
||||
terminal::{StdinMsg, Terminal},
|
||||
config::periphery_config, docker::DockerClient,
|
||||
helpers::resolve_host_public_ip, stats::StatsClient,
|
||||
terminal::PeripheryTerminal,
|
||||
};
|
||||
|
||||
/// Should call in startup to ensure Periphery errors without valid private key.
|
||||
@@ -167,16 +168,38 @@ pub fn stats_client() -> &'static RwLock<StatsClient> {
|
||||
STATS_CLIENT.get_or_init(|| RwLock::new(StatsClient::default()))
|
||||
}
|
||||
|
||||
pub type PtyName = String;
|
||||
pub type PtyMap =
|
||||
tokio::sync::RwLock<HashMap<PtyName, Arc<Terminal>>>;
|
||||
|
||||
pub fn terminals() -> &'static PtyMap {
|
||||
static TERMINALS: OnceLock<PtyMap> = OnceLock::new();
|
||||
pub fn terminals() -> &'static CloneVecCache<Arc<PeripheryTerminal>> {
|
||||
static TERMINALS: OnceLock<CloneVecCache<Arc<PeripheryTerminal>>> =
|
||||
OnceLock::new();
|
||||
TERMINALS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type TerminalChannels = CloneCache<Uuid, Arc<TerminalChannel>>;
|
||||
#[derive(Default)]
|
||||
pub struct TerminalChannels(CloneCache<Uuid, Arc<TerminalChannel>>);
|
||||
|
||||
impl TerminalChannels {
|
||||
pub async fn get(
|
||||
&self,
|
||||
channel: &Uuid,
|
||||
) -> Option<Arc<TerminalChannel>> {
|
||||
self.0.get(channel).await
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
&self,
|
||||
channel: Uuid,
|
||||
terminal: Arc<TerminalChannel>,
|
||||
) -> Option<Arc<TerminalChannel>> {
|
||||
self.0.insert(channel, terminal).await
|
||||
}
|
||||
|
||||
pub async fn remove(&self, channel: &Uuid) {
|
||||
let Some(channel) = self.0.remove(channel).await else {
|
||||
return;
|
||||
};
|
||||
channel.cancel.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminal_channels() -> &'static TerminalChannels {
|
||||
static TERMINAL_CHANNELS: OnceLock<TerminalChannels> =
|
||||
@@ -186,7 +209,7 @@ pub fn terminal_channels() -> &'static TerminalChannels {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TerminalChannel {
|
||||
pub sender: mpsc::Sender<StdinMsg>,
|
||||
pub sender: mpsc::Sender<TerminalStdinMessage>,
|
||||
pub cancel: CancellationToken,
|
||||
}
|
||||
|
||||
@@ -297,3 +320,18 @@ pub fn container_stats() -> &'static ArcSwap<ContainerStatsMap> {
|
||||
OnceLock::new();
|
||||
CONTAINER_STATS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn host_public_ip() -> Option<&'static String> {
|
||||
static PUBLIC_IPS: OnceCell<Option<String>> = OnceCell::const_new();
|
||||
PUBLIC_IPS
|
||||
.get_or_init(|| async {
|
||||
resolve_host_public_ip()
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("Failed to resolve host public ip | {e:#}")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
+172
-111
@@ -3,54 +3,72 @@ use std::{collections::VecDeque, sync::Arc, time::Duration};
|
||||
use anyhow::{Context, anyhow};
|
||||
use bytes::Bytes;
|
||||
use encoding::{Decode as _, WithChannel};
|
||||
use komodo_client::{
|
||||
api::write::TerminalRecreateMode,
|
||||
entities::{ContainerTerminalMode, server::TerminalInfo},
|
||||
use komodo_client::entities::{
|
||||
komodo_timestamp,
|
||||
terminal::{
|
||||
Terminal, TerminalMessage, TerminalRecreateMode,
|
||||
TerminalStdinMessage, TerminalTarget,
|
||||
},
|
||||
};
|
||||
use periphery_client::transport::EncodedTerminalMessage;
|
||||
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::state::{terminal_channels, terminal_triggers, terminals};
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
state::{terminal_channels, terminal_triggers, terminals},
|
||||
};
|
||||
|
||||
pub async fn handle_message(message: EncodedTerminalMessage) {
|
||||
let WithChannel {
|
||||
channel: channel_id,
|
||||
mut data,
|
||||
data,
|
||||
} = match message.decode() {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("Received invalid Terminal bytes | {e:#}");
|
||||
warn!(
|
||||
"Received invalid Terminal bytes | Channel decode | {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let msg = match data.first() {
|
||||
Some(&0x00) => StdinMsg::Bytes(data.drain(1..).collect()),
|
||||
Some(&0xFF) => {
|
||||
if let Ok(dimensions) =
|
||||
serde_json::from_slice::<ResizeDimensions>(&data[1..])
|
||||
{
|
||||
StdinMsg::Resize(dimensions)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = match data {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
// This means Core should disconnect.
|
||||
terminal_channels().remove(&channel_id).await;
|
||||
return;
|
||||
}
|
||||
Some(_) => StdinMsg::Bytes(data),
|
||||
// Empty bytes are the "begin" trigger for Terminal Executions
|
||||
None => {
|
||||
};
|
||||
|
||||
let message = match TerminalMessage::from_raw(data)
|
||||
.into_stdin_message()
|
||||
{
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Received invalid Terminal bytes | TerminalMessage decode | {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Send 'begin' trigger for Terminal Executions
|
||||
Ok(TerminalStdinMessage::Begin) => {
|
||||
if let Err(e) = terminal_triggers().send(&channel_id).await {
|
||||
warn!("{e:#}")
|
||||
}
|
||||
return;
|
||||
}
|
||||
Ok(message) => message,
|
||||
};
|
||||
|
||||
let Some(channel) = terminal_channels().get(&channel_id).await
|
||||
else {
|
||||
warn!("No terminal channel for {channel_id}");
|
||||
return;
|
||||
};
|
||||
if let Err(e) = channel.sender.send(msg).await {
|
||||
|
||||
if let Err(e) = channel.sender.send(message).await {
|
||||
warn!("No receiver for {channel_id} | {e:?}");
|
||||
};
|
||||
}
|
||||
@@ -58,17 +76,25 @@ pub async fn handle_message(message: EncodedTerminalMessage) {
|
||||
#[instrument("CreateTerminalInner", skip_all, fields(name))]
|
||||
pub async fn create_terminal(
|
||||
name: String,
|
||||
command: String,
|
||||
target: TerminalTarget,
|
||||
command: Option<String>,
|
||||
recreate: TerminalRecreateMode,
|
||||
container: Option<(String, ContainerTerminalMode)>,
|
||||
) -> anyhow::Result<Arc<Terminal>> {
|
||||
) -> anyhow::Result<Arc<PeripheryTerminal>> {
|
||||
let command = command.unwrap_or_else(|| {
|
||||
periphery_config().default_terminal_command.clone()
|
||||
});
|
||||
trace!(
|
||||
"CreateTerminal: {name} | command: {command} | recreate: {recreate:?}"
|
||||
);
|
||||
let mut terminals = terminals().write().await;
|
||||
let terminals = terminals();
|
||||
use TerminalRecreateMode::*;
|
||||
if matches!(recreate, Never | DifferentCommand)
|
||||
&& let Some(terminal) = terminals.get(&name)
|
||||
&& let Some(terminal) = terminals
|
||||
.find(|terminal| {
|
||||
terminal.target.matches_on_server(&target)
|
||||
&& terminal.name == name
|
||||
})
|
||||
.await
|
||||
{
|
||||
if terminal.command == command {
|
||||
return Ok(terminal.clone());
|
||||
@@ -80,46 +106,88 @@ pub async fn create_terminal(
|
||||
}
|
||||
}
|
||||
let terminal = Arc::new(
|
||||
Terminal::new(command, container)
|
||||
PeripheryTerminal::new(name.clone(), target.clone(), command)
|
||||
.await
|
||||
.context("Failed to init terminal")?,
|
||||
);
|
||||
if let Some(prev) = terminals.insert(name, terminal.clone()) {
|
||||
if let Some(prev) = terminals
|
||||
.insert(
|
||||
|terminal| {
|
||||
terminal.target.matches_on_server(&target)
|
||||
&& terminal.name == name
|
||||
},
|
||||
terminal.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
prev.cancel();
|
||||
}
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
#[instrument("DeleteTerminalInner")]
|
||||
pub async fn delete_terminal(name: &str) {
|
||||
if let Some(terminal) = terminals().write().await.remove(name) {
|
||||
pub async fn delete_terminal(target: &TerminalTarget, name: &str) {
|
||||
if let Some(terminal) = terminals()
|
||||
.remove(|terminal| {
|
||||
terminal.target.matches_on_server(target)
|
||||
&& name == terminal.name.as_str()
|
||||
})
|
||||
.await
|
||||
{
|
||||
terminal.cancel.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_terminals(
|
||||
container: Option<&str>,
|
||||
) -> Vec<TerminalInfo> {
|
||||
target: Option<&TerminalTarget>,
|
||||
) -> Vec<Terminal> {
|
||||
let mut terminals = terminals()
|
||||
.read()
|
||||
.list()
|
||||
.await
|
||||
.iter()
|
||||
.filter(|(_, terminal)| {
|
||||
// If no container passed, returns all
|
||||
let Some(container) = container else {
|
||||
.filter(|terminal| {
|
||||
// If no target passed, returns all
|
||||
let Some(target) = target else {
|
||||
return true;
|
||||
};
|
||||
let Some(term_container) =
|
||||
terminal.container.as_ref().map(|c| c.0.as_str())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
term_container == container
|
||||
match (target, &terminal.target) {
|
||||
(
|
||||
TerminalTarget::Server { .. },
|
||||
TerminalTarget::Server { .. },
|
||||
) => true,
|
||||
(
|
||||
TerminalTarget::Container {
|
||||
container: target_container,
|
||||
..
|
||||
},
|
||||
TerminalTarget::Container { container, .. },
|
||||
) => target_container == container,
|
||||
(
|
||||
TerminalTarget::Stack {
|
||||
stack: target_stack,
|
||||
service: target_service,
|
||||
},
|
||||
TerminalTarget::Stack { stack, service },
|
||||
) => {
|
||||
target_stack == stack
|
||||
// If no service passed, only match on stack
|
||||
&& (target_service.is_none() || target_service == service)
|
||||
}
|
||||
(
|
||||
TerminalTarget::Deployment {
|
||||
deployment: target_deployment,
|
||||
},
|
||||
TerminalTarget::Deployment { deployment },
|
||||
) => target_deployment == deployment,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.map(|(name, terminal)| TerminalInfo {
|
||||
name: name.to_string(),
|
||||
.map(|terminal| Terminal {
|
||||
name: terminal.name.clone(),
|
||||
target: terminal.target.clone(),
|
||||
command: terminal.command.clone(),
|
||||
stored_size_kb: terminal.history.size_kb(),
|
||||
created_at: terminal.created_at,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
terminals.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
@@ -128,81 +196,75 @@ pub async fn list_terminals(
|
||||
|
||||
pub async fn get_terminal(
|
||||
name: &str,
|
||||
) -> anyhow::Result<Arc<Terminal>> {
|
||||
target: &TerminalTarget,
|
||||
) -> anyhow::Result<Arc<PeripheryTerminal>> {
|
||||
terminals()
|
||||
.read()
|
||||
.find(|terminal| {
|
||||
terminal.target.matches_on_server(target)
|
||||
&& terminal.name.as_str() == name
|
||||
})
|
||||
.await
|
||||
.get(name)
|
||||
.cloned()
|
||||
.with_context(|| format!("No terminal at {name}"))
|
||||
.with_context(|| format!("No terminal for {target:?} at {name}"))
|
||||
}
|
||||
|
||||
pub async fn clean_up_terminals() {
|
||||
terminals()
|
||||
.write()
|
||||
.await
|
||||
.retain(|_, terminal| !terminal.cancel.is_cancelled());
|
||||
.retain(|terminal| !terminal.cancel.is_cancelled())
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_all_terminals() {
|
||||
terminals()
|
||||
.write()
|
||||
.await
|
||||
.drain()
|
||||
.for_each(|(_, terminal)| terminal.cancel());
|
||||
.retain(|terminal| {
|
||||
terminal.cancel();
|
||||
false
|
||||
})
|
||||
.await;
|
||||
// The terminals poll cancel every 500 millis, need to wait for them
|
||||
// to finish cancelling.
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Deserialize)]
|
||||
pub struct ResizeDimensions {
|
||||
rows: u16,
|
||||
cols: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum StdinMsg {
|
||||
Bytes(Vec<u8>),
|
||||
Resize(ResizeDimensions),
|
||||
}
|
||||
|
||||
pub type StdinSender = mpsc::Sender<StdinMsg>;
|
||||
pub type StdinSender = mpsc::Sender<TerminalStdinMessage>;
|
||||
pub type StdoutReceiver = broadcast::Receiver<Bytes>;
|
||||
|
||||
pub struct Terminal {
|
||||
/// The command that was used as the root command, eg `shell`
|
||||
command: String,
|
||||
pub struct PeripheryTerminal {
|
||||
/// The name of the terminal.
|
||||
pub name: String,
|
||||
/// The target resource of the Terminal.
|
||||
pub target: TerminalTarget,
|
||||
/// The command used to init the shell.
|
||||
pub command: String,
|
||||
/// When the Terminal was created.
|
||||
pub created_at: i64,
|
||||
|
||||
pub cancel: CancellationToken,
|
||||
|
||||
pub stdin: StdinSender,
|
||||
pub stdout: StdoutReceiver,
|
||||
|
||||
pub history: Arc<History>,
|
||||
|
||||
/// If terminal is for a container.
|
||||
pub container: Option<(String, ContainerTerminalMode)>,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
impl PeripheryTerminal {
|
||||
async fn new(
|
||||
name: String,
|
||||
target: TerminalTarget,
|
||||
command: String,
|
||||
container: Option<(String, ContainerTerminalMode)>,
|
||||
) -> anyhow::Result<Terminal> {
|
||||
trace!("Creating terminal with command: {command}");
|
||||
) -> anyhow::Result<PeripheryTerminal> {
|
||||
trace!("Creating Terminal | Command: {command}");
|
||||
|
||||
let terminal = native_pty_system()
|
||||
.openpty(PtySize::default())
|
||||
.context("Failed to open terminal")?;
|
||||
|
||||
let mut command_split = command.split(' ').map(|arg| arg.trim());
|
||||
let cmd =
|
||||
command_split.next().context("Command cannot be empty")?;
|
||||
let mut lexed = shlex::split(&command)
|
||||
.context("Invalid command: empty")?
|
||||
.into_iter();
|
||||
|
||||
let cmd = lexed.next().context("Command cannot be empty")?;
|
||||
|
||||
let mut cmd = CommandBuilder::new(cmd);
|
||||
|
||||
for arg in command_split {
|
||||
for arg in lexed {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
@@ -249,27 +311,27 @@ impl Terminal {
|
||||
break;
|
||||
}
|
||||
match child.try_wait() {
|
||||
Ok(None) => {
|
||||
// Continue
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
Ok(Some(code)) => {
|
||||
debug!("child exited with code {code}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("failed to wait for child | {e:?}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel if loop broken
|
||||
_cancel.cancel();
|
||||
});
|
||||
|
||||
// WS (channel) -> STDIN TASK
|
||||
// Theres only one consumer here, so use mpsc
|
||||
let (stdin, mut channel_read) =
|
||||
tokio::sync::mpsc::channel::<StdinMsg>(8192);
|
||||
let (stdin, mut stdin_read) = tokio::sync::mpsc::channel(8192);
|
||||
let _cancel = cancel.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
loop {
|
||||
@@ -277,15 +339,16 @@ impl Terminal {
|
||||
trace!("terminal write: cancelled from outside");
|
||||
break;
|
||||
}
|
||||
match channel_read.blocking_recv() {
|
||||
Some(StdinMsg::Bytes(bytes)) => {
|
||||
match stdin_read.blocking_recv() {
|
||||
// Handled in self::handle_message
|
||||
Some(TerminalStdinMessage::Begin) => {}
|
||||
Some(TerminalStdinMessage::Forward(bytes)) => {
|
||||
if let Err(e) = terminal_write.write_all(&bytes) {
|
||||
debug!("Failed to write to PTY: {e:?}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(StdinMsg::Resize(dimensions)) => {
|
||||
Some(TerminalStdinMessage::Resize(dimensions)) => {
|
||||
if let Err(e) = terminal.master.resize(PtySize {
|
||||
cols: dimensions.cols,
|
||||
rows: dimensions.rows,
|
||||
@@ -293,24 +356,24 @@ impl Terminal {
|
||||
pixel_height: 0,
|
||||
}) {
|
||||
debug!("Failed to resize | {e:?}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
};
|
||||
}
|
||||
None => {
|
||||
debug!("WS -> PTY channel read error: Disconnected");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel if loop broken
|
||||
_cancel.cancel();
|
||||
});
|
||||
|
||||
let history = Arc::new(History::default());
|
||||
|
||||
// PTY -> WS (channel) TASK
|
||||
// Uses broadcast to output to multiple client simultaneously
|
||||
let (write, stdout) =
|
||||
let (write_stdout, stdout) =
|
||||
tokio::sync::broadcast::channel::<Bytes>(8192);
|
||||
let _cancel = cancel.clone();
|
||||
let _history = history.clone();
|
||||
@@ -322,40 +385,38 @@ impl Terminal {
|
||||
break;
|
||||
}
|
||||
match terminal_read.read(&mut buf) {
|
||||
Ok(0) => {
|
||||
// EOF
|
||||
trace!("Got PTY read EOF");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
Ok(0) => break, // EOF
|
||||
Ok(n) => {
|
||||
_history.push(&buf[..n]);
|
||||
let slice = &buf[..n];
|
||||
_history.push(slice);
|
||||
if let Err(e) =
|
||||
write.send(Bytes::copy_from_slice(&buf[..n]))
|
||||
write_stdout.send(Bytes::copy_from_slice(slice))
|
||||
{
|
||||
debug!("PTY -> WS channel send error: {e:?}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to read for PTY: {e:?}");
|
||||
_cancel.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel if loop broken
|
||||
_cancel.cancel();
|
||||
});
|
||||
|
||||
trace!("terminal tasks spawned");
|
||||
|
||||
Ok(Terminal {
|
||||
Ok(PeripheryTerminal {
|
||||
name,
|
||||
target,
|
||||
command,
|
||||
cancel,
|
||||
stdin,
|
||||
stdout,
|
||||
history,
|
||||
container,
|
||||
created_at: komodo_timestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ resolver_api.workspace = true
|
||||
# external
|
||||
tokio-tungstenite.workspace = true
|
||||
derive_builder.workspace = true
|
||||
futures-util.workspace = true
|
||||
urlencoding.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio-util.workspace = true
|
||||
@@ -36,15 +37,14 @@ thiserror.workspace = true
|
||||
typeshare.workspace = true
|
||||
indexmap.workspace = true
|
||||
serde_qs.workspace = true
|
||||
futures.workspace = true
|
||||
reqwest.workspace = true
|
||||
tracing.workspace = true
|
||||
anyhow.workspace = true
|
||||
bytes.workspace = true
|
||||
serde.workspace = true
|
||||
tokio.workspace = true
|
||||
strum.workspace = true
|
||||
bson.workspace = true
|
||||
clap.workspace = true
|
||||
envy.workspace = true
|
||||
uuid.workspace = true
|
||||
rand.workspace = true
|
||||
@@ -61,6 +61,10 @@ pub struct ListAllDockerContainers {
|
||||
/// Filter by server id or name.
|
||||
#[serde(default)]
|
||||
pub servers: Vec<String>,
|
||||
|
||||
/// Filter by container name.
|
||||
#[serde(default)]
|
||||
pub containers: Vec<String>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -20,6 +20,7 @@ mod server;
|
||||
mod stack;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod terminal;
|
||||
mod toml;
|
||||
mod update;
|
||||
mod user;
|
||||
@@ -43,6 +44,7 @@ pub use server::*;
|
||||
pub use stack::*;
|
||||
pub use sync::*;
|
||||
pub use tag::*;
|
||||
pub use terminal::*;
|
||||
pub use toml::*;
|
||||
pub use update::*;
|
||||
pub use user::*;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::entities::{
|
||||
I64, Timelength,
|
||||
server::{
|
||||
PeripheryInformation, Server, ServerActionState, ServerListItem,
|
||||
ServerQuery, ServerState, TerminalInfo,
|
||||
ServerQuery, ServerState,
|
||||
},
|
||||
stats::{
|
||||
SystemInformation, SystemProcess, SystemStats, SystemStatsRecord,
|
||||
@@ -265,27 +265,3 @@ pub struct GetServersSummaryResponse {
|
||||
/// The number of disabled servers.
|
||||
pub disabled: I64,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// List the current terminals on specified server.
|
||||
/// Response: [ListTerminalsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(ListTerminalsResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct ListTerminals {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub server: String,
|
||||
/// Force a fresh call to Periphery for the list.
|
||||
/// Otherwise the response will be cached for 30s
|
||||
#[serde(default)]
|
||||
pub fresh: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListTerminalsResponse = Vec<TerminalInfo>;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::terminal::{Terminal, TerminalTarget};
|
||||
|
||||
use super::KomodoReadRequest;
|
||||
|
||||
//
|
||||
|
||||
/// List Terminals.
|
||||
/// Response: [ListTerminalsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(ListTerminalsResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct ListTerminals {
|
||||
/// Filter the Terminals returned by the Target.
|
||||
pub target: Option<TerminalTarget>,
|
||||
/// Return results with resource names instead of ids.
|
||||
#[serde(default)]
|
||||
pub use_names: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListTerminalsResponse = Vec<Terminal>;
|
||||
@@ -1,175 +1,61 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::api::write::TerminalRecreateMode;
|
||||
use crate::entities::terminal::{
|
||||
ContainerTerminalMode, TerminalRecreateMode, TerminalTarget,
|
||||
};
|
||||
|
||||
/// Query to connect to a terminal (interactive shell over websocket) on the given server.
|
||||
/// Connect to a Terminal.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectTerminalQuery {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// Each periphery can keep multiple terminals open.
|
||||
/// If a terminals with the specified name does not exist,
|
||||
/// the call will fail.
|
||||
/// Create a terminal using [CreateTerminal][super::write::server::CreateTerminal]
|
||||
pub terminal: String,
|
||||
/// The target to create terminal for.
|
||||
pub target: TerminalTarget,
|
||||
/// Terminal name to connect to.
|
||||
/// If it may not exist yet, also pass 'init' params
|
||||
/// to include initialization.
|
||||
/// Default: Depends on target.
|
||||
pub terminal: Option<String>,
|
||||
/// Pass to init the terminal session
|
||||
/// for when the terminal doesn't already exist.
|
||||
///
|
||||
/// Example: ?...(query)&init[command]=bash&init[recreate]=DifferentCommand
|
||||
pub init: Option<InitTerminal>,
|
||||
}
|
||||
|
||||
/// Args to init the Terminal if needed.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InitTerminal {
|
||||
/// The shell command (eg `bash`) to init the shell.
|
||||
///
|
||||
/// Default:
|
||||
/// - Server: Configured on each Periphery
|
||||
/// - Container: `sh`
|
||||
pub command: Option<String>,
|
||||
/// Default: `Never`
|
||||
#[serde(default)]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
/// Only relevant for container-type terminals.
|
||||
/// Specify the container terminal mode (`exec` or `attach`).
|
||||
/// Default: `exec`
|
||||
pub mode: Option<ContainerTerminalMode>,
|
||||
}
|
||||
|
||||
/// Execute a terminal command on the given server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ExecuteTerminalBody {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The name of the terminal on the server to use to execute.
|
||||
pub terminal: String,
|
||||
/// The target to create terminal for.
|
||||
pub target: TerminalTarget,
|
||||
/// Terminal name to connect to.
|
||||
/// If it may not exist yet, also pass 'init' params
|
||||
/// to include initialization.
|
||||
/// Default: Depends on target.
|
||||
pub terminal: Option<String>,
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
/// Query to connect to a container exec session (interactive shell over websocket) on the given server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectContainerExecQuery {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The container name
|
||||
pub container: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Query to connect to a container attach session (interactive shell over websocket) on the given server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectContainerAttachQuery {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The container name
|
||||
pub container: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Execute a command in the given containers shell.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ExecuteContainerExecBody {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The container name
|
||||
pub container: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Query to connect to a container exec session (interactive shell over websocket) on the given Deployment.
|
||||
/// This call will use access to the Deployment Terminal to permission the call.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectDeploymentExecQuery {
|
||||
/// Deployment Id or name
|
||||
pub deployment: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Query to connect to a container attach session (interactive shell over websocket) on the given Deployment.
|
||||
/// This call will use access to the Deployment Terminal to permission the call.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectDeploymentAttachQuery {
|
||||
/// Deployment Id or name
|
||||
pub deployment: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Execute a command in the given containers shell.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ExecuteDeploymentExecBody {
|
||||
/// Deployment Id or name
|
||||
pub deployment: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Query to connect to a container exec session (interactive shell over websocket) on the given Stack / service.
|
||||
/// This call will use access to the Stack Terminal to permission the call.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectStackExecQuery {
|
||||
/// Stack Id or name
|
||||
pub stack: String,
|
||||
/// The service name to connect to
|
||||
pub service: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Query to connect to a container attach session (interactive shell over websocket) on the given Stack / service.
|
||||
/// This call will use access to the Stack Terminal to permission the call.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConnectStackAttachQuery {
|
||||
/// Stack Id or name
|
||||
pub stack: String,
|
||||
/// The service name to attach to
|
||||
pub service: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
/// Execute a command in the given containers shell.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ExecuteStackExecBody {
|
||||
/// Stack Id or name
|
||||
pub stack: String,
|
||||
/// The service name to connect to
|
||||
pub service: String,
|
||||
/// The shell to use (eg. `sh` or `bash`)
|
||||
pub shell: String,
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
/// Specify the recreate behavior.
|
||||
/// Default is 'DifferentCommand'
|
||||
#[serde(default = "default_container_recreate_mode")]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
fn default_container_recreate_mode() -> TerminalRecreateMode {
|
||||
TerminalRecreateMode::DifferentCommand
|
||||
/// Pass to init the terminal session
|
||||
/// for when the terminal doesn't already exist.
|
||||
pub init: Option<InitTerminal>,
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ mod server;
|
||||
mod stack;
|
||||
mod sync;
|
||||
mod tags;
|
||||
mod terminal;
|
||||
mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
@@ -36,6 +37,7 @@ pub use server::*;
|
||||
pub use stack::*;
|
||||
pub use sync::*;
|
||||
pub use tags::*;
|
||||
pub use terminal::*;
|
||||
pub use user::*;
|
||||
pub use user_group::*;
|
||||
pub use variable::*;
|
||||
|
||||
@@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
NoData,
|
||||
server::{_PartialServerConfig, Server},
|
||||
update::Update,
|
||||
};
|
||||
@@ -131,88 +130,6 @@ pub struct CreateNetwork {
|
||||
|
||||
//
|
||||
|
||||
/// Configures the behavior of [CreateTerminal] if the
|
||||
/// specified terminal name already exists.
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub enum TerminalRecreateMode {
|
||||
/// Never kill the old terminal if it already exists.
|
||||
/// If the command is different, returns error.
|
||||
#[default]
|
||||
Never,
|
||||
/// Always kill the old terminal and create new one
|
||||
Always,
|
||||
/// Only kill and recreate if the command is different.
|
||||
DifferentCommand,
|
||||
}
|
||||
|
||||
/// Create a terminal on the server.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct CreateTerminal {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The name of the terminal on the server to create.
|
||||
pub name: String,
|
||||
/// The shell command (eg `bash`) to init the shell.
|
||||
///
|
||||
/// This can also include args:
|
||||
/// `docker exec -it container sh`
|
||||
///
|
||||
/// Default: `bash`
|
||||
#[serde(default = "default_command")]
|
||||
pub command: String,
|
||||
/// Default: `Never`
|
||||
#[serde(default)]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
fn default_command() -> String {
|
||||
String::from("bash")
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete a terminal on the server.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct DeleteTerminal {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
/// The name of the terminal on the server to delete.
|
||||
pub terminal: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete all terminals on the server.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct DeleteAllTerminals {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Updates the Server with an explicit Public Key.
|
||||
/// Response: [Update]
|
||||
#[typeshare]
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
NoData,
|
||||
server::ServerQuery,
|
||||
terminal::{
|
||||
ContainerTerminalMode, TerminalRecreateMode, TerminalTarget,
|
||||
},
|
||||
};
|
||||
|
||||
use super::KomodoWriteRequest;
|
||||
|
||||
//
|
||||
|
||||
/// Create a Terminal.
|
||||
/// Requires minimum Read + Terminal permission on the target Resource.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct CreateTerminal {
|
||||
/// A name for the Terminal session.
|
||||
pub name: String,
|
||||
/// The target to create terminal for
|
||||
pub target: TerminalTarget,
|
||||
/// The shell command (eg `bash`) to init the shell.
|
||||
///
|
||||
/// Default:
|
||||
/// - Server: Configured on each Periphery
|
||||
/// - ContainerExec: `sh`
|
||||
/// - Attach: unused
|
||||
pub command: Option<String>,
|
||||
/// For container terminals, choose 'exec' or 'attach'.
|
||||
///
|
||||
/// Default
|
||||
/// - Server: ignored
|
||||
/// - Container / Stack / Deployment: `exec`
|
||||
pub mode: Option<ContainerTerminalMode>,
|
||||
/// Default: `Never`
|
||||
#[serde(default)]
|
||||
pub recreate: TerminalRecreateMode,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete a terminal.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct DeleteTerminal {
|
||||
/// Server / Container / Stack / Deployment
|
||||
pub target: TerminalTarget,
|
||||
/// The name of the Terminal to delete.
|
||||
pub terminal: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete all Terminals on the Server.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct DeleteAllTerminals {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete all terminals on many or all Servers.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(NoData)]
|
||||
#[error(serror::Error)]
|
||||
pub struct BatchDeleteAllTerminals {
|
||||
/// Optional structured query to filter servers.
|
||||
#[serde(default)]
|
||||
pub query: ServerQuery,
|
||||
}
|
||||
@@ -98,6 +98,9 @@ pub enum ListCommand {
|
||||
/// List Syncs (aliases: `sync`, `sn`)
|
||||
#[clap(alias = "sync", alias = "sn")]
|
||||
Syncs(ResourceFilters),
|
||||
/// List Terminals (aliases: `term`, `tm`)
|
||||
#[clap(alias = "term", alias = "tm")]
|
||||
Terminals(ResourceFilters),
|
||||
/// List scheduled Procedures / Actions (aliases: `sched`, `sc`)
|
||||
#[clap(alias = "sched", alias = "sc")]
|
||||
Schedules(ResourceFilters),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::api::execute::Execution;
|
||||
pub mod container;
|
||||
pub mod database;
|
||||
pub mod list;
|
||||
pub mod terminal;
|
||||
pub mod update;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
@@ -51,28 +52,6 @@ pub enum Command {
|
||||
unsanitized: bool,
|
||||
},
|
||||
|
||||
/// Private-Public key utilities. (alias: `k`)
|
||||
#[clap(alias = "k")]
|
||||
Key {
|
||||
#[command(subcommand)]
|
||||
command: crate::entities::config::KeyCommand,
|
||||
},
|
||||
|
||||
/// Database utilities. (alias: `db`)
|
||||
#[clap(alias = "db")]
|
||||
Database {
|
||||
#[command(subcommand)]
|
||||
command: database::DatabaseCommand,
|
||||
},
|
||||
|
||||
/// Container info (aliases: `ps`, `cn`, `containers`)
|
||||
#[clap(alias = "ps", alias = "cn", alias = "containers")]
|
||||
Container(container::Container),
|
||||
|
||||
/// Inspect containers (alias: `i`)
|
||||
#[clap(alias = "i")]
|
||||
Inspect(container::InspectContainer),
|
||||
|
||||
/// List Komodo resources (aliases: `ls`, `resources`)
|
||||
#[clap(alias = "ls", alias = "resources")]
|
||||
List(list::List),
|
||||
@@ -93,6 +72,38 @@ pub enum Command {
|
||||
#[command(subcommand)]
|
||||
command: update::UpdateCommand,
|
||||
},
|
||||
|
||||
/// Container info (aliases: `ps`, `cn`, `containers`)
|
||||
#[clap(alias = "ps", alias = "cn", alias = "containers")]
|
||||
Container(container::Container),
|
||||
|
||||
/// Inspect containers (alias: `i`)
|
||||
#[clap(alias = "i")]
|
||||
Inspect(container::InspectContainer),
|
||||
|
||||
/// Connect to Server Terminals. (alias: `ssh`)
|
||||
#[clap(alias = "ssh")]
|
||||
Connect(terminal::Connect),
|
||||
|
||||
/// Connect to Container Terminals. `docker exec` analogue.
|
||||
Exec(terminal::Exec),
|
||||
|
||||
/// Attach to Container Terminals. `docker attach` analogue.
|
||||
Attach(terminal::Attach),
|
||||
|
||||
/// Private-Public key utilities. (alias: `k`)
|
||||
#[clap(alias = "k")]
|
||||
Key {
|
||||
#[command(subcommand)]
|
||||
command: crate::entities::config::KeyCommand,
|
||||
},
|
||||
|
||||
/// Database utilities. (alias: `db`)
|
||||
#[clap(alias = "db")]
|
||||
Database {
|
||||
#[command(subcommand)]
|
||||
command: database::DatabaseCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct Connect {
|
||||
/// The server to connect to.
|
||||
pub server: String,
|
||||
|
||||
/// Custom command to use to start the shell, eg `bash`.
|
||||
/// Defaults to Periphery default.
|
||||
pub command: Option<String>,
|
||||
|
||||
/// The terminal name to connect to. Default: `ssh`
|
||||
#[arg(long, short = 'n', default_value_t = String::from("ssh"))]
|
||||
pub name: String,
|
||||
|
||||
/// Force fresh terminal to replace existing one.
|
||||
#[arg(long, short = 'r', default_value_t = false)]
|
||||
pub recreate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct Exec {
|
||||
/// The container (name) to connect to.
|
||||
/// Will error if matches multiple containers but no Server is defined.
|
||||
pub container: String,
|
||||
/// The shell, eg `bash`.
|
||||
pub shell: String,
|
||||
/// Specify Server.
|
||||
/// Required if multiple servers have same container name.
|
||||
/// (alias: `s`)
|
||||
#[arg(long, short = 's')]
|
||||
pub server: Option<String>,
|
||||
/// Force fresh terminal to replace existing one.
|
||||
#[arg(long, short = 'r', default_value_t = false)]
|
||||
pub recreate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct Attach {
|
||||
/// The container (name) to attach to.
|
||||
/// Will error if matches multiple containers but no Server is defined.
|
||||
pub container: String,
|
||||
/// Specify Server.
|
||||
/// Required if multiple servers have same container name.
|
||||
/// (alias: `s`)
|
||||
#[arg(long, short = 's')]
|
||||
pub server: Option<String>,
|
||||
/// Force fresh terminal to replace existing one.
|
||||
#[arg(long, short = 'r', default_value_t = false)]
|
||||
pub recreate: bool,
|
||||
}
|
||||
@@ -173,6 +173,8 @@ pub struct Env {
|
||||
pub periphery_stack_dir: Option<PathBuf>,
|
||||
/// Override `build_dir`
|
||||
pub periphery_build_dir: Option<PathBuf>,
|
||||
/// Override `default_terminal_command`
|
||||
pub periphery_default_terminal_command: Option<String>,
|
||||
/// Override `disable_terminals`
|
||||
pub periphery_disable_terminals: Option<bool>,
|
||||
/// Override `disable_container_terminals`
|
||||
@@ -356,6 +358,12 @@ pub struct PeripheryConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build_dir: Option<PathBuf>,
|
||||
|
||||
/// Configure the default terminal command
|
||||
/// when one isn't provided.
|
||||
/// Default: `bash`
|
||||
#[serde(default = "default_default_terminal_command")]
|
||||
pub default_terminal_command: String,
|
||||
|
||||
/// Whether to disable the create terminal
|
||||
/// and disallow direct remote shell access.
|
||||
/// Default: false
|
||||
@@ -431,6 +439,10 @@ fn default_root_directory() -> PathBuf {
|
||||
"/etc/komodo".parse().unwrap()
|
||||
}
|
||||
|
||||
fn default_default_terminal_command() -> String {
|
||||
String::from("bash")
|
||||
}
|
||||
|
||||
fn default_stats_polling_rate() -> Timelength {
|
||||
Timelength::FiveSeconds
|
||||
}
|
||||
@@ -460,6 +472,7 @@ impl Default for PeripheryConfig {
|
||||
repo_dir: None,
|
||||
stack_dir: None,
|
||||
build_dir: None,
|
||||
default_terminal_command: default_default_terminal_command(),
|
||||
disable_terminals: Default::default(),
|
||||
disable_container_terminals: Default::default(),
|
||||
stats_polling_rate: default_stats_polling_rate(),
|
||||
@@ -510,6 +523,7 @@ impl PeripheryConfig {
|
||||
repo_dir: self.repo_dir.clone(),
|
||||
stack_dir: self.stack_dir.clone(),
|
||||
build_dir: self.build_dir.clone(),
|
||||
default_terminal_command: self.default_terminal_command.clone(),
|
||||
disable_terminals: self.disable_terminals,
|
||||
disable_container_terminals: self.disable_container_terminals,
|
||||
stats_polling_rate: self.stats_polling_rate,
|
||||
|
||||
@@ -66,6 +66,8 @@ pub mod stats;
|
||||
pub mod sync;
|
||||
/// Subtypes of [Tag][tag::Tag].
|
||||
pub mod tag;
|
||||
|
||||
pub mod terminal;
|
||||
/// Subtypes of [ResourcesToml][toml::ResourcesToml].
|
||||
pub mod toml;
|
||||
/// Subtypes of [Update][update::Update].
|
||||
@@ -605,28 +607,6 @@ pub struct RepoExecutionResponse {
|
||||
pub commit_message: Option<String>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ContainerTerminalMode {
|
||||
#[default]
|
||||
Exec,
|
||||
Attach,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user