Compare commits

..
43 Commits
Author SHA1 Message Date
mbecker20 a3a01f1625 add schemas for swarm commands 2025-11-27 23:47:22 -08:00
mbecker20 aec8fa2bf1 task view page with logs 2025-11-23 23:45:28 -08:00
mbecker20 7ff2dba82f swarm service page with logs 2025-11-23 12:48:23 -08:00
mbecker20 9c86b2d239 swarm service logs api 2025-11-23 00:43:59 -08:00
mbecker20 b1fec7c663 docker swarm stack read apis 2025-11-22 16:05:51 -08:00
mbecker20 8341c6b802 sward resources use list items 2025-11-22 02:41:49 -08:00
mbecker20 73285c4374 move swarm to list item + inspect on pages 2025-11-21 14:34:20 -08:00
mbecker20 32d48cdb02 swarm configs view 2025-11-21 13:21:14 -08:00
mbecker20 8e081fd09c swarm config apis 2025-11-21 12:55:37 -08:00
mbecker20 04531f1dea Explore swarm info 2025-11-20 13:00:29 -08:00
mbecker20 80f439d472 basic swarm 2025-11-19 18:02:50 -08:00
mbecker20 d5e03d6d16 prog on swarm 2025-11-19 01:48:43 -08:00
mbecker20 a9f55bb8e6 fix Terminals permissions when no perm on Server 2025-11-17 15:44:14 -08:00
mbecker20 9e765f93f5 bump deps 2025-11-17 15:43:56 -08:00
mbecker20 b3aa8e906f add context to onboarding login error and error messages sent over the communication websocket in general 2025-11-17 13:23:44 -08:00
mbecker20 03fe442aa0 in prog 2025-11-17 12:59:08 -08:00
ChanningHeandGitHub d268009a6a validate compose_cmd_wrapper for required placeholder and add interpolation support (#977) 2025-11-12 10:09:15 -08:00
mbecker20 f0697e812a shift + N open new variable dialog 2025-11-11 14:22:56 -08:00
mbecker20 78766463d6 create variable Enter submit 2025-11-11 14:18:28 -08:00
mbecker20 0fa1edba2c deploy 2.0.0-dev-90 2025-11-11 14:13:55 -08:00
mbecker20 bbd968cac3 bump toml pretty with fix syncing procedure executions with multiline batch patterns 2025-11-11 14:13:25 -08:00
mbecker20 5f24fc1be3 deploy 2.0.0-dev-89 2025-11-11 00:44:49 -08:00
mbecker20 7ecd2b0b0b improve cmd wrapper with comment removal support 2025-11-11 00:43:54 -08:00
mbecker20 7bf44d2e04 fix some broken tabs 2025-11-11 00:35:24 -08:00
mbecker20 24e0672384 dashboard resets page title 2025-11-11 00:18:16 -08:00
mbecker20 04f081631f deploy 2.0.0-dev-88 2025-11-11 00:16:07 -08:00
mbecker20 b1af956b63 fix dashboard pie chart code splitting issue 2025-11-11 00:05:32 -08:00
mbecker20 370712b29f gen served client types 2025-11-11 00:05:02 -08:00
mbecker20 2b6c552964 canius lite update 2025-11-11 00:04:52 -08:00
mbecker20 434a1d8ea9 clippy lint 2025-11-11 00:04:39 -08:00
ChanningHeandGitHub 0b7f28360f Add optional command wrapper for Docker Compose in StackConfig (#973) 2025-11-10 23:59:09 -08:00
ChanningHeandGitHub 3c8ef0ab29 Add track option for Additional Env Files (#955) 2025-11-10 23:47:07 -08:00
mbecker20 930b2423c3 deploy 2.0.0-dev-87 2025-11-07 10:33:23 -08:00
mbecker20 546747b5f2 add timeout to dns ip resolve, only use ipv4 2025-11-07 10:32:55 -08:00
mbecker20 c6df866755 better aws builder config organization 2025-11-07 10:04:45 -08:00
mbecker20 ea5e684915 better useUserTargetPermissions 2025-11-06 22:18:31 -08:00
mbecker20 64db8933de RefreshBuildCache after build 2025-11-04 00:27:34 -08:00
mbecker20 7a5580de57 builder uppercase login 2025-11-04 00:06:46 -08:00
mbecker20 b1656bb174 log about enabling user linger 2025-11-03 10:29:50 -08:00
Badal SinghandGitHub 559ce103da Update setup-periphery.py (#958) 2025-11-03 09:57:49 -08:00
mbecker20 75e278a57b builder fix partial_default 2025-10-30 00:41:27 -07:00
mbecker20 430f3ddc34 fix omni search container double select on same name 2025-10-29 00:02:32 -07:00
mbecker20 6c30c202e9 add Terminals to omni search 2025-10-28 23:59:41 -07:00
144 changed files with 13725 additions and 2322 deletions
Generated
+135 -112
View File
@@ -65,6 +65,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -138,9 +144,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "async-compression"
version = "0.4.32"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0"
checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2"
dependencies = [
"compression-codecs",
"compression-core",
@@ -187,9 +193,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-config"
version = "1.8.8"
version = "1.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8"
checksum = "1856b1b48b65f71a4dd940b1c0931f9a7b646d4a924b9828ffefc1454714668a"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -217,9 +223,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
version = "1.2.8"
version = "1.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc"
checksum = "86590e57ea40121d47d3f2e131bfd873dea15d78dc2f4604f4734537ad9e56c4"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -254,9 +260,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.5.12"
version = "1.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d"
checksum = "8fe0fd441565b0b318c76e7206c8d1d0b0166b3e986cf30e890b61feb6192045"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -278,9 +284,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.176.0"
version = "1.188.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171b12a84d9c7b43b75bf2ae99e86ce40ff0b5ecc8194a67d547e55c1ad2438e"
checksum = "ce23f4f9971b682019024aed0bf0fd971df7bf86519e41cf2f7ec7a631049395"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -301,9 +307,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.86.0"
version = "1.89.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0abbfab841446cce6e87af853a3ba2cc1bc9afcd3f3550dd556c43d434c86d"
checksum = "a9c1b1af02288f729e95b72bd17988c009aa72e26dcb59b3200f86d7aea726c9"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -323,9 +329,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.88.0"
version = "1.91.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a68d675582afea0e94d38b6ca9c5aaae4ca14f1d36faa6edb19b42e687e70d7"
checksum = "4e8122301558dc7c6c68e878af918880b82ff41897a60c8c4e18e4dc4d93e9f1"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -345,9 +351,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.88.0"
version = "1.92.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d30990923f4f675523c51eb1c0dec9b752fb267b36a61e83cbc219c9d86da715"
checksum = "a0c7808adcff8333eaa76a849e6de926c6ac1a1268b9fd6afe32de9c29ef29d2"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -368,9 +374,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.3.5"
version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68"
checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -401,15 +407,16 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
version = "0.62.4"
version = "0.62.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3feafd437c763db26aa04e0cc7591185d0961e64c61885bece0fb9d50ceac671"
checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"bytes",
"bytes-utils",
"futures-core",
"futures-util",
"http 0.2.12",
"http 1.3.1",
"http-body 0.4.6",
@@ -421,9 +428,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http-client"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1053b5e587e6fa40ce5a79ea27957b04ba660baa02b28b7436f64850152234f1"
checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -440,7 +447,7 @@ dependencies = [
"hyper-util",
"pin-project-lite",
"rustls 0.21.12",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -451,9 +458,9 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
version = "0.61.6"
version = "0.61.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff418fc8ec5cadf8173b10125f05c2e7e1d46771406187b2c878557d4503390"
checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54"
dependencies = [
"aws-smithy-types",
]
@@ -479,9 +486,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.9.3"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ab99739082da5347660c556689256438defae3bcefd66c52b095905730e404"
checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -503,9 +510,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3683c5b152d2ad753607179ed71988e8cfd52964443b4f74fd8e552d0bbfeb46"
checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -520,9 +527,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.3.3"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5b3a7486f6690ba25952cabf1e7d75e34d69eaff5081904a47bc79074d6457"
checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e"
dependencies = [
"base64-simd",
"bytes",
@@ -546,18 +553,18 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
version = "0.60.11"
version = "0.60.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c34127e8c624bc2999f3b657e749c1393bedc9cd97b92a804db8ced4d2e163"
checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
version = "1.3.9"
version = "1.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1"
checksum = "d79fb68e3d7fe5d4833ea34dc87d2e97d26d3086cb3da660bb6b1f76d98680b6"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -569,9 +576,9 @@ dependencies = [
[[package]]
name = "axum"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
dependencies = [
"axum-core",
"axum-macros",
@@ -625,13 +632,14 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.10.3"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
checksum = "dbfe9f610fe4e99cf0cfcd03ccf8c63c28c616fe714d80475ef731f3b13dd21b"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-core",
"futures-util",
"headers",
"http 1.3.1",
@@ -639,8 +647,6 @@ dependencies = [
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"serde_core",
"tower-layer",
"tower-service",
"tracing",
@@ -659,9 +665,9 @@ dependencies = [
[[package]]
name = "axum-server"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab"
checksum = "c1ab4a3ec9ea8a657c72d99a03a824af695bd0fb5ec639ccbd9cd3543b41a5f9"
dependencies = [
"arc-swap",
"bytes",
@@ -671,7 +677,7 @@ dependencies = [
"hyper 1.7.0",
"hyper-util",
"pin-project-lite",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"tokio",
@@ -806,9 +812,9 @@ dependencies = [
[[package]]
name = "bollard"
version = "0.19.3"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7646ee90964aa59e9f832a67182791396a19a5b1d76eb17599a8310a7e2e09"
checksum = "87a52479c9237eb04047ddb94788c41ca0d26eaff8b697ecfbb4c32f7fdc3b1b"
dependencies = [
"base64 0.22.1",
"bollard-stubs",
@@ -886,9 +892,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "bytes-utils"
@@ -902,7 +908,7 @@ dependencies = [
[[package]]
name = "cache"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"tokio",
@@ -1019,9 +1025,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1029,9 +1035,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1"
dependencies = [
"anstream",
"anstyle",
@@ -1094,7 +1100,7 @@ dependencies = [
[[package]]
name = "command"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"komodo_client",
"shlex",
@@ -1104,9 +1110,9 @@ dependencies = [
[[package]]
name = "compression-codecs"
version = "0.4.31"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23"
checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b"
dependencies = [
"compression-core",
"flate2",
@@ -1115,13 +1121,13 @@ dependencies = [
[[package]]
name = "compression-core"
version = "0.4.29"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb"
checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582"
[[package]]
name = "config"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"colored",
"indexmap 2.12.0",
@@ -1227,9 +1233,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "croner"
version = "3.0.0"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c007081651a19b42931f86f7d4f74ee1c2a7d0cd2c6636a81695b5ffd4e9990"
checksum = "4aa42bcd3d846ebf66e15bd528d1087f75d1c6c1c66ebff626178a106353c576"
dependencies = [
"chrono",
"derive_builder",
@@ -1443,7 +1449,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "database"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"async-compression",
@@ -1466,7 +1472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"pem-rfc7468",
"pem-rfc7468 0.7.0",
"zeroize",
]
@@ -1732,7 +1738,7 @@ dependencies = [
"generic-array",
"group",
"hkdf",
"pem-rfc7468",
"pem-rfc7468 0.7.0",
"pkcs8",
"rand_core 0.6.4",
"sec1",
@@ -1742,7 +1748,7 @@ dependencies = [
[[package]]
name = "encoding"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"bytes",
@@ -1784,7 +1790,7 @@ dependencies = [
[[package]]
name = "environment"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"formatting",
@@ -1794,7 +1800,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"thiserror 2.0.17",
]
@@ -1879,6 +1885,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -1890,7 +1902,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"serror",
]
@@ -2056,7 +2068,7 @@ dependencies = [
[[package]]
name = "git"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"cache",
@@ -2127,21 +2139,23 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
@@ -2454,7 +2468,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.7.0",
"hyper-util",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -2688,7 +2702,7 @@ dependencies = [
[[package]]
name = "interpolate"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"komodo_client",
@@ -2793,9 +2807,9 @@ dependencies = [
[[package]]
name = "jsonwebtoken"
version = "10.1.0"
version = "10.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b"
checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e"
dependencies = [
"aws-lc-rs",
"base64 0.22.1",
@@ -2810,7 +2824,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"chrono",
@@ -2838,7 +2852,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2874,7 +2888,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"arc-swap",
@@ -2921,7 +2935,7 @@ dependencies = [
"reqwest",
"resolver_api",
"response",
"rustls 0.23.34",
"rustls 0.23.35",
"secret_file",
"serde",
"serde_json",
@@ -2948,7 +2962,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"arc-swap",
@@ -2979,7 +2993,7 @@ dependencies = [
"periphery_client",
"portable-pty",
"resolver_api",
"rustls 0.23.34",
"rustls 0.23.35",
"secret_file",
"serde",
"serde_json",
@@ -3069,7 +3083,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "logger"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"komodo_client",
@@ -3300,7 +3314,7 @@ dependencies = [
"percent-encoding",
"rand 0.8.5",
"rustc_version_runtime",
"rustls 0.23.34",
"rustls 0.23.35",
"rustversion",
"serde",
"serde_bytes",
@@ -3361,7 +3375,7 @@ dependencies = [
[[package]]
name = "noise"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"arc-swap",
@@ -3369,7 +3383,7 @@ dependencies = [
"colored",
"der",
"komodo_client",
"pem-rfc7468",
"pem-rfc7468 1.0.0",
"pkcs8",
"secret_file",
"serde_json",
@@ -3659,11 +3673,11 @@ dependencies = [
[[package]]
name = "ordered_hash_map"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176"
checksum = "a6c699f8a30f345785be969deed7eee4c73a5de58c7faf61d6a3251ef798ff61"
dependencies = [
"hashbrown 0.13.2",
"hashbrown 0.15.5",
"serde",
]
@@ -3768,6 +3782,15 @@ dependencies = [
"base64ct",
]
[[package]]
name = "pem-rfc7468"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -3776,7 +3799,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "periphery_client"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"derive_variants",
@@ -3996,7 +4019,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.34",
"rustls 0.23.35",
"socket2 0.6.1",
"thiserror 2.0.17",
"tokio",
@@ -4016,7 +4039,7 @@ dependencies = [
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pki-types",
"slab",
"thiserror 2.0.17",
@@ -4208,7 +4231,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"serde",
@@ -4257,7 +4280,7 @@ dependencies = [
[[package]]
name = "response"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"axum",
@@ -4362,9 +4385,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.34"
version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"aws-lc-rs",
"log",
@@ -4527,7 +4550,7 @@ dependencies = [
[[package]]
name = "secret_file"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"tokio",
]
@@ -5273,7 +5296,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls 0.23.34",
"rustls 0.23.35",
"tokio",
]
@@ -5297,7 +5320,7 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -5307,9 +5330,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.16"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -5354,9 +5377,9 @@ dependencies = [
[[package]]
name = "toml_pretty"
version = "1.2.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd195be7600b4b26e951a25b382b67a976d2d44bb967a1a5b573a3e63d18c29"
checksum = "abc44e8fe3b5ec4cce2db87a3c11e0ebc396d4c2d59da64d6f0c00e78f9b9296"
dependencies = [
"ordered_hash_map",
"serde",
@@ -5560,7 +5583,7 @@ dependencies = [
[[package]]
name = "transport"
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
dependencies = [
"anyhow",
"axum",
@@ -5573,7 +5596,7 @@ dependencies = [
"periphery_client",
"pin-project-lite",
"rand 0.9.2",
"rustls 0.23.34",
"rustls 0.23.35",
"serde",
"serror",
"sha1",
@@ -5604,7 +5627,7 @@ dependencies = [
"httparse",
"log",
"rand 0.9.2",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pki-types",
"sha1",
"thiserror 2.0.17",
+18 -18
View File
@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "2.0.0-dev-86"
version = "2.0.0-dev-91"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -48,14 +48,14 @@ partial_derive2 = "0.4.3"
derive_variants = "1.0.0"
mongo_indexed = "2.0.2"
resolver_api = "3.0.0"
toml_pretty = "1.2.0"
toml_pretty = "2.0.0"
mungos = "3.2.2"
svi = "1.2.0"
# ASYNC
reqwest = { version = "0.12.24", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
tokio = { version = "1.48.0", features = ["full"] }
tokio-util = { version = "0.7.16", features = ["io", "codec"] }
tokio-util = { version = "0.7.17", features = ["io", "codec"] }
tokio-stream = { version = "0.1.17", features = ["sync"] }
pin-project-lite = "0.2.16"
futures-util = "0.3.31"
@@ -63,10 +63,10 @@ arc-swap = "1.7.1"
# SERVER
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
axum-extra = { version = "0.10.3", features = ["typed-header"] }
axum-extra = { version = "0.12.2", features = ["typed-header"] }
tower-http = { version = "0.6.6", features = ["fs", "cors"] }
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
axum = { version = "0.8.6", features = ["ws", "json", "macros"] }
axum-server = { version = "0.7.3", features = ["tls-rustls"] }
axum = { version = "0.8.7", features = ["ws", "json", "macros"] }
# SER/DE
ipnetwork = { version = "0.21.1", features = ["serde"] }
@@ -94,16 +94,16 @@ opentelemetry = "0.31.0"
tracing = "0.1.41"
# CONFIG
clap = { version = "4.5.50", features = ["derive"] }
clap = { version = "4.5.52", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
# CRYPTO / AUTH
openidconnect = { version = "4.0.1", features = ["accept-rfc3339-timestamps"] }
uuid = { version = "1.18.1", features = ["v4", "fast-rng", "serde"] }
jsonwebtoken = { version = "10.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"
jsonwebtoken = { version = "10.2.0", features = ["aws_lc_rs"] } # locked back with octorust
rustls = { version = "0.23.35", features = ["aws-lc-rs"] }
pem-rfc7468 = { version = "1.0.0", features = ["alloc"] }
urlencoding = "2.1.3"
bcrypt = "0.17.1"
base64 = "0.22.1"
@@ -122,28 +122,28 @@ hickory-resolver = "0.25.2"
portable-pty = "0.9.0"
shell-escape = "0.1.5"
crossterm = "0.29.0"
bollard = "0.19.3"
bollard = "0.19.4"
sysinfo = "0.37.1"
shlex = "1.3.0"
# CLOUD
aws-config = "1.8.8"
aws-sdk-ec2 = "1.176.0"
aws-credential-types = "1.2.8"
aws-config = "1.8.10"
aws-sdk-ec2 = "1.188.0"
aws-credential-types = "1.2.9"
## CRON
english-to-cron = "0.1.6"
chrono-tz = "0.10.4"
chrono = "0.4.42"
croner = "3.0.0"
croner = "3.0.1"
# MISC
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
async-compression = { version = "0.4.33", features = ["tokio", "gzip"] }
derive_builder = "0.20.2"
comfy-table = "7.2.1"
typeshare = "1.0.4"
dashmap = "6.1.0"
wildcard = "0.3.0"
colored = "3.0.0"
bytes = "1.10.1"
bytes = "1.11.0"
regex = "1.12.2"
+32 -19
View File
@@ -17,9 +17,12 @@ use formatting::format_serror;
use futures_util::future::join_all;
use interpolate::Interpolator;
use komodo_client::{
api::execute::{
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
RunBuild,
api::{
execute::{
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
RunBuild,
},
write::RefreshBuildCache,
},
entities::{
alert::{Alert, AlertData, SeverityLevel},
@@ -41,6 +44,7 @@ use uuid::Uuid;
use crate::{
alert::send_alerts,
api::write::WriteArgs,
helpers::{
build_git_token,
builder::{cleanup_builder_instance, connect_builder_periphery},
@@ -186,7 +190,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
update.finalize();
let id = update.id.clone();
if let Err(e) = update_update(update).await {
warn!("failed to modify Update {id} on db | {e:#}");
warn!("Failed to modify Update {id} on db | {e:#}");
}
if !is_server_builder {
cancel_clone.cancel();
@@ -215,12 +219,12 @@ impl Resolve<ExecuteArgs> for RunBuild {
Ok(builder) => builder,
Err(e) => {
warn!(
"failed to get builder for build {} | {e:#}",
"Failed to get Builder for Build {} | {e:#}",
build.name
);
update.logs.push(Log::error(
"get builder",
format_serror(&e.context("failed to get builder").into()),
"Get Builder",
format_serror(&e.context("Failed to get Builder").into()),
));
return handle_early_return(
update, build.id, build.name, false,
@@ -276,7 +280,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
let commit_message = match res {
Ok(res) => {
debug!("finished repo clone");
debug!("Finished repo clone");
update.logs.extend(res.res.logs);
update.commit_hash =
res.res.commit_hash.unwrap_or_default().to_string();
@@ -312,10 +316,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
commit_hash: optional_string(&update.commit_hash),
// Unused for now
additional_tags: Default::default(),
}) => res.context("failed at call to periphery to build"),
}) => res.context("Failed at call to Periphery to build"),
_ = cancel.cancelled() => {
info!("Build cancelled during build, cleaning up builder");
update.push_error_log("Build cancelled", String::from("user cancelled build during docker build"));
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
return handle_early_return(update, build.id, build.name, true).await
@@ -328,10 +332,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
update.logs.extend(logs);
}
Err(e) => {
warn!("error in build | {e:#}");
warn!("Error in build | {e:#}");
update.push_error_log(
"Build Error",
format_serror(&e.context("failed to build").into()),
format_serror(&e.context("Failed to build").into()),
)
}
};
@@ -382,12 +386,15 @@ impl Resolve<ExecuteArgs> for RunBuild {
update_update(update.clone()).await?;
let Build { id, name, .. } = build;
if update.success {
// don't hold response up for user
tokio::spawn(async move {
handle_post_build_redeploy(&build.id).await;
handle_post_build_redeploy(&id).await;
});
} else {
let name = name.clone();
let target = update.target.clone();
let version = update.version;
tokio::spawn(async move {
@@ -398,16 +405,22 @@ impl Resolve<ExecuteArgs> for RunBuild {
resolved_ts: Some(komodo_timestamp()),
resolved: true,
level: SeverityLevel::Warning,
data: AlertData::BuildFailed {
id: build.id,
name: build.name,
version,
},
data: AlertData::BuildFailed { id, name, version },
};
send_alerts(&[alert]).await
});
}
if let Err(e) = (RefreshBuildCache { build: name })
.resolve(&WriteArgs { user: user.clone() })
.await
{
update.push_error_log(
"Refresh build cache",
format_serror(&e.error.into()),
);
}
Ok(update.clone())
}
}
@@ -565,7 +578,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
.await
{
warn!(
"failed to set CancelBuild Update status Complete after timeout | {e:#}"
"Failed to set CancelBuild Update status Complete after timeout | {e:#}"
)
}
});
+3 -3
View File
@@ -142,13 +142,13 @@ pub enum ExecuteRequest {
RunAction(RunAction),
BatchRunAction(BatchRunAction),
// ==== SYNC ====
RunSync(RunSync),
// ==== ALERTER ====
TestAlerter(TestAlerter),
SendAlert(SendAlert),
// ==== SYNC ====
RunSync(RunSync),
// ==== MAINTENANCE ====
ClearRepoCache(ClearRepoCache),
BackupCoreDatabase(BackupCoreDatabase),
+31 -27
View File
@@ -136,34 +136,10 @@ impl Resolve<ExecuteArgs> for RunSync {
};
match ObjectId::from_str(&name_or_id) {
Ok(_) => match resource_type {
ResourceTargetVariant::Alerter => all_resources
.alerters
ResourceTargetVariant::Swarm => all_resources
.swarms
.get(&name_or_id)
.map(|a| a.name.clone()),
ResourceTargetVariant::Build => all_resources
.builds
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Builder => all_resources
.builders
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Deployment => all_resources
.deployments
.get(&name_or_id)
.map(|d| d.name.clone()),
ResourceTargetVariant::Procedure => all_resources
.procedures
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::Action => all_resources
.actions
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::Repo => all_resources
.repos
.get(&name_or_id)
.map(|r| r.name.clone()),
.map(|s| s.name.clone()),
ResourceTargetVariant::Server => all_resources
.servers
.get(&name_or_id)
@@ -172,10 +148,38 @@ impl Resolve<ExecuteArgs> for RunSync {
.stacks
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Deployment => all_resources
.deployments
.get(&name_or_id)
.map(|d| d.name.clone()),
ResourceTargetVariant::Build => all_resources
.builds
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Repo => all_resources
.repos
.get(&name_or_id)
.map(|r| r.name.clone()),
ResourceTargetVariant::Procedure => all_resources
.procedures
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::Action => all_resources
.actions
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::ResourceSync => all_resources
.syncs
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Builder => all_resources
.builders
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Alerter => all_resources
.alerters
.get(&name_or_id)
.map(|a| a.name.clone()),
ResourceTargetVariant::System => None,
},
Err(_) => Some(name_or_id),
+54 -31
View File
@@ -49,6 +49,7 @@ mod repo;
mod schedule;
mod server;
mod stack;
mod swarm;
mod sync;
mod tag;
mod terminal;
@@ -75,36 +76,27 @@ enum ReadRequest {
ListGitProvidersFromConfig(ListGitProvidersFromConfig),
ListDockerRegistriesFromConfig(ListDockerRegistriesFromConfig),
// ==== USER ====
GetUsername(GetUsername),
GetPermission(GetPermission),
FindUser(FindUser),
ListUsers(ListUsers),
ListApiKeys(ListApiKeys),
ListApiKeysForServiceUser(ListApiKeysForServiceUser),
ListPermissions(ListPermissions),
ListUserTargetPermissions(ListUserTargetPermissions),
// ==== USER GROUP ====
GetUserGroup(GetUserGroup),
ListUserGroups(ListUserGroups),
// ==== PROCEDURE ====
GetProceduresSummary(GetProceduresSummary),
GetProcedure(GetProcedure),
GetProcedureActionState(GetProcedureActionState),
ListProcedures(ListProcedures),
ListFullProcedures(ListFullProcedures),
// ==== ACTION ====
GetActionsSummary(GetActionsSummary),
GetAction(GetAction),
GetActionActionState(GetActionActionState),
ListActions(ListActions),
ListFullActions(ListFullActions),
// ==== SCHEDULE ====
ListSchedules(ListSchedules),
// ==== SWARM ====
GetSwarmsSummary(GetSwarmsSummary),
GetSwarm(GetSwarm),
GetSwarmActionState(GetSwarmActionState),
ListSwarms(ListSwarms),
InspectSwarm(InspectSwarm),
ListFullSwarms(ListFullSwarms),
ListSwarmNodes(ListSwarmNodes),
InspectSwarmNode(InspectSwarmNode),
ListSwarmConfigs(ListSwarmConfigs),
InspectSwarmConfig(InspectSwarmConfig),
ListSwarmSecrets(ListSwarmSecrets),
InspectSwarmSecret(InspectSwarmSecret),
ListSwarmStacks(ListSwarmStacks),
InspectSwarmStack(InspectSwarmStack),
ListSwarmTasks(ListSwarmTasks),
InspectSwarmTask(InspectSwarmTask),
ListSwarmServices(ListSwarmServices),
InspectSwarmService(InspectSwarmService),
GetSwarmServiceLog(GetSwarmServiceLog),
SearchSwarmServiceLog(SearchSwarmServiceLog),
// ==== SERVER ====
GetServersSummary(GetServersSummary),
@@ -112,7 +104,6 @@ enum ReadRequest {
GetServerState(GetServerState),
GetPeripheryInformation(GetPeripheryInformation),
GetServerActionState(GetServerActionState),
GetHistoricalServerStats(GetHistoricalServerStats),
ListServers(ListServers),
ListFullServers(ListFullServers),
@@ -139,6 +130,7 @@ enum ReadRequest {
// ==== SERVER STATS ====
GetSystemInformation(GetSystemInformation),
GetSystemStats(GetSystemStats),
GetHistoricalServerStats(GetHistoricalServerStats),
ListSystemProcesses(ListSystemProcesses),
// ==== STACK ====
@@ -184,6 +176,23 @@ enum ReadRequest {
ListRepos(ListRepos),
ListFullRepos(ListFullRepos),
// ==== PROCEDURE ====
GetProceduresSummary(GetProceduresSummary),
GetProcedure(GetProcedure),
GetProcedureActionState(GetProcedureActionState),
ListProcedures(ListProcedures),
ListFullProcedures(ListFullProcedures),
// ==== ACTION ====
GetActionsSummary(GetActionsSummary),
GetAction(GetAction),
GetActionActionState(GetActionActionState),
ListActions(ListActions),
ListFullActions(ListFullActions),
// ==== SCHEDULE ====
ListSchedules(ListSchedules),
// ==== SYNC ====
GetResourceSyncsSummary(GetResourceSyncsSummary),
GetResourceSync(GetResourceSync),
@@ -211,6 +220,20 @@ enum ReadRequest {
GetTag(GetTag),
ListTags(ListTags),
// ==== USER ====
GetUsername(GetUsername),
GetPermission(GetPermission),
FindUser(FindUser),
ListUsers(ListUsers),
ListApiKeys(ListApiKeys),
ListApiKeysForServiceUser(ListApiKeysForServiceUser),
ListPermissions(ListPermissions),
ListUserTargetPermissions(ListUserTargetPermissions),
// ==== USER GROUP ====
GetUserGroup(GetUserGroup),
ListUserGroups(ListUserGroups),
// ==== UPDATE ====
GetUpdate(GetUpdate),
ListUpdates(ListUpdates),
+15 -14
View File
@@ -383,8 +383,8 @@ impl Resolve<ReadArgs> for ListDockerContainers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(containers) = &cache.containers {
Ok(containers.clone())
if let Some(docker) = &cache.docker {
Ok(docker.containers.clone())
} else {
Ok(Vec::new())
}
@@ -410,10 +410,11 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
let Some(more) = &cache.containers else {
let Some(docker) = &cache.docker else {
continue;
};
let more = more
let more = docker
.containers
.iter()
.filter(|container| {
self.containers.is_empty()
@@ -448,8 +449,8 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
.get_or_insert_default(&server.id)
.await;
if let Some(containers) = &cache.containers {
for container in containers {
if let Some(docker) = &cache.docker {
for container in &docker.containers {
res.total += 1;
match container.state {
ContainerStateStatusEnum::Created
@@ -641,8 +642,8 @@ impl Resolve<ReadArgs> for ListDockerNetworks {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(networks) = &cache.networks {
Ok(networks.clone())
if let Some(docker) = &cache.docker {
Ok(docker.networks.clone())
} else {
Ok(Vec::new())
}
@@ -694,8 +695,8 @@ impl Resolve<ReadArgs> for ListDockerImages {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(images) = &cache.images {
Ok(images.clone())
if let Some(docker) = &cache.docker {
Ok(docker.images.clone())
} else {
Ok(Vec::new())
}
@@ -775,8 +776,8 @@ impl Resolve<ReadArgs> for ListDockerVolumes {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(volumes) = &cache.volumes {
Ok(volumes.clone())
if let Some(docker) = &cache.docker {
Ok(docker.volumes.clone())
} else {
Ok(Vec::new())
}
@@ -825,8 +826,8 @@ impl Resolve<ReadArgs> for ListComposeProjects {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(projects) = &cache.projects {
Ok(projects.clone())
if let Some(docker) = &cache.docker {
Ok(docker.projects.clone())
} else {
Ok(Vec::new())
}
+478
View File
@@ -0,0 +1,478 @@
use anyhow::Context;
use komodo_client::{
api::read::*,
entities::{
permission::PermissionLevel,
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
},
};
use resolver_api::Resolve;
use crate::{
helpers::{query::get_all_tags, swarm::swarm_request},
permission::get_check_permissions,
resource,
state::{action_states, swarm_status_cache},
};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetSwarm {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Swarm> {
Ok(
get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?,
)
}
}
impl Resolve<ReadArgs> for ListSwarms {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<SwarmListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
Ok(
resource::list_for_user::<Swarm>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
impl Resolve<ReadArgs> for ListFullSwarms {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullSwarmsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
Ok(
resource::list_full_for_user::<Swarm>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
impl Resolve<ReadArgs> for GetSwarmActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SwarmActionState> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let action_state = action_states()
.swarm
.get(&swarm.id)
.await
.unwrap_or_default()
.get()?;
Ok(action_state)
}
}
impl Resolve<ReadArgs> for GetSwarmsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSwarmsSummaryResponse> {
let swarms = resource::list_full_for_user::<Swarm>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
.context("failed to get swarms from db")?;
let mut res = GetSwarmsSummaryResponse::default();
let cache = swarm_status_cache();
for swarm in swarms {
res.total += 1;
match cache
.get(&swarm.id)
.await
.map(|status| status.state)
.unwrap_or_default()
{
SwarmState::Unknown => {
res.unknown += 1;
}
SwarmState::Healthy => {
res.healthy += 1;
}
SwarmState::Unhealthy => {
res.unhealthy += 1;
}
}
}
Ok(res)
}
}
impl Resolve<ReadArgs> for InspectSwarm {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
let inspect = cache
.inspect
.as_ref()
.cloned()
.context("SwarmInspectInfo not available")?;
Ok(inspect)
}
}
impl Resolve<ReadArgs> for ListSwarmNodes {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmNodesResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.nodes.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmNode {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmNodeResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmNode {
node: self.node,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for ListSwarmServices {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmServicesResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.services.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmService {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmServiceResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmService {
service: self.service,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for GetSwarmServiceLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSwarmServiceLogResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.logs(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLog {
service: self.service,
tail: self.tail,
timestamps: self.timestamps,
no_task_ids: self.no_task_ids,
no_resolve: self.no_resolve,
details: self.details,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for SearchSwarmServiceLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SearchSwarmServiceLogResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.logs(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLogSearch {
service: self.service,
terms: self.terms,
combinator: self.combinator,
invert: self.invert,
timestamps: self.timestamps,
no_task_ids: self.no_task_ids,
no_resolve: self.no_resolve,
details: self.details,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for ListSwarmTasks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmTasksResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.tasks.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmTask {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmTaskResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmTask {
task: self.task,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for ListSwarmSecrets {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmSecretsResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.secrets.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmSecret {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmSecretResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmSecret {
secret: self.secret,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for ListSwarmConfigs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmConfigsResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.configs.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmConfig {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmConfigResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmConfig {
config: self.config,
},
)
.await
.map_err(Into::into)
}
}
impl Resolve<ReadArgs> for ListSwarmStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmStacksResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache =
swarm_status_cache().get_or_insert_default(&swarm.id).await;
if let Some(lists) = &cache.lists {
Ok(lists.stacks.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectSwarmStack {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmStackResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Read.into(),
)
.await?;
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmStack {
stack: self.stack,
},
)
.await
.map_err(Into::into)
}
}
+78 -62
View File
@@ -11,7 +11,8 @@ use komodo_client::{
builder::Builder, deployment::Deployment,
permission::PermissionLevel, procedure::Procedure, repo::Repo,
resource::ResourceQuery, server::Server, stack::Stack,
sync::ResourceSync, toml::ResourcesToml, user::User,
swarm::Swarm, sync::ResourceSync, toml::ResourcesToml,
user::User,
},
};
use resolver_api::Resolve;
@@ -207,42 +208,21 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
let ReadArgs { user } = args;
for target in targets {
match target {
ResourceTarget::Alerter(id) => {
let mut alerter = get_check_permissions::<Alerter>(
ResourceTarget::Swarm(id) => {
let mut swarm = get_check_permissions::<Swarm>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Alerter::replace_ids(&mut alerter);
res.alerters.push(convert_resource::<Alerter>(
alerter,
Swarm::replace_ids(&mut swarm);
res.swarms.push(convert_resource::<Swarm>(
swarm,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::ResourceSync(id) => {
let mut sync = get_check_permissions::<ResourceSync>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
if sync.config.file_contents.is_empty()
&& (sync.config.files_on_host
|| !sync.config.repo.is_empty()
|| !sync.config.linked_repo.is_empty())
{
ResourceSync::replace_ids(&mut sync);
res.resource_syncs.push(convert_resource::<ResourceSync>(
sync,
false,
vec![],
&id_to_tags,
))
}
}
ResourceTarget::Server(id) => {
let mut server = get_check_permissions::<Server>(
&id,
@@ -258,31 +238,16 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
&id_to_tags,
))
}
ResourceTarget::Builder(id) => {
let mut builder = get_check_permissions::<Builder>(
ResourceTarget::Stack(id) => {
let mut stack = get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Builder::replace_ids(&mut builder);
res.builders.push(convert_resource::<Builder>(
builder,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Build(id) => {
let mut build = get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Build::replace_ids(&mut build);
res.builds.push(convert_resource::<Build>(
build,
Stack::replace_ids(&mut stack);
res.stacks.push(convert_resource::<Stack>(
stack,
false,
vec![],
&id_to_tags,
@@ -303,6 +268,21 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
&id_to_tags,
))
}
ResourceTarget::Build(id) => {
let mut build = get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Build::replace_ids(&mut build);
res.builds.push(convert_resource::<Build>(
build,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Repo(id) => {
let mut repo = get_check_permissions::<Repo>(
&id,
@@ -318,21 +298,6 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
&id_to_tags,
))
}
ResourceTarget::Stack(id) => {
let mut stack = get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Stack::replace_ids(&mut stack);
res.stacks.push(convert_resource::<Stack>(
stack,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Procedure(id) => {
let mut procedure = get_check_permissions::<Procedure>(
&id,
@@ -363,6 +328,57 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
&id_to_tags,
));
}
ResourceTarget::ResourceSync(id) => {
let mut sync = get_check_permissions::<ResourceSync>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
if sync.config.file_contents.is_empty()
&& (sync.config.files_on_host
|| !sync.config.repo.is_empty()
|| !sync.config.linked_repo.is_empty())
{
ResourceSync::replace_ids(&mut sync);
res.resource_syncs.push(convert_resource::<ResourceSync>(
sync,
false,
vec![],
&id_to_tags,
))
}
}
ResourceTarget::Builder(id) => {
let mut builder = get_check_permissions::<Builder>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Builder::replace_ids(&mut builder);
res.builders.push(convert_resource::<Builder>(
builder,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Alerter(id) => {
let mut alerter = get_check_permissions::<Alerter>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Alerter::replace_ids(&mut alerter);
res.alerters.push(convert_resource::<Alerter>(
alerter,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::System(_) => continue,
};
}
+27 -18
View File
@@ -20,6 +20,7 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
swarm::Swarm,
sync::ResourceSync,
update::{Update, UpdateListItem},
user::User,
@@ -266,6 +267,14 @@ impl Resolve<ReadArgs> for GetUpdate {
anyhow!("user must be admin to view system updates").into(),
);
}
ResourceTarget::Swarm(id) => {
get_check_permissions::<Swarm>(
id,
user,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Server(id) => {
get_check_permissions::<Server>(
id,
@@ -274,6 +283,14 @@ impl Resolve<ReadArgs> for GetUpdate {
)
.await?;
}
ResourceTarget::Stack(id) => {
get_check_permissions::<Stack>(
id,
user,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Deployment(id) => {
get_check_permissions::<Deployment>(
id,
@@ -298,22 +315,6 @@ impl Resolve<ReadArgs> for GetUpdate {
)
.await?;
}
ResourceTarget::Builder(id) => {
get_check_permissions::<Builder>(
id,
user,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Alerter(id) => {
get_check_permissions::<Alerter>(
id,
user,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Procedure(id) => {
get_check_permissions::<Procedure>(
id,
@@ -338,8 +339,16 @@ impl Resolve<ReadArgs> for GetUpdate {
)
.await?;
}
ResourceTarget::Stack(id) => {
get_check_permissions::<Stack>(
ResourceTarget::Builder(id) => {
get_check_permissions::<Builder>(
id,
user,
PermissionLevel::Read.into(),
)
.await?;
}
ResourceTarget::Alerter(id) => {
get_check_permissions::<Alerter>(
id,
user,
PermissionLevel::Read.into(),
+59 -51
View File
@@ -32,6 +32,7 @@ mod resource;
mod server;
mod service_user;
mod stack;
mod swarm;
mod sync;
mod tag;
mod terminal;
@@ -53,36 +54,16 @@ pub struct WriteArgs {
#[error(serror::Error)]
#[serde(tag = "type", content = "params")]
pub enum WriteRequest {
// ==== USER ====
CreateLocalUser(CreateLocalUser),
UpdateUserUsername(UpdateUserUsername),
UpdateUserPassword(UpdateUserPassword),
DeleteUser(DeleteUser),
// ==== SERVICE USER ====
CreateServiceUser(CreateServiceUser),
UpdateServiceUserDescription(UpdateServiceUserDescription),
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
// ==== USER GROUP ====
CreateUserGroup(CreateUserGroup),
RenameUserGroup(RenameUserGroup),
DeleteUserGroup(DeleteUserGroup),
AddUserToUserGroup(AddUserToUserGroup),
RemoveUserFromUserGroup(RemoveUserFromUserGroup),
SetUsersInUserGroup(SetUsersInUserGroup),
SetEveryoneUserGroup(SetEveryoneUserGroup),
// ==== PERMISSIONS ====
UpdateUserAdmin(UpdateUserAdmin),
UpdateUserBasePermissions(UpdateUserBasePermissions),
UpdatePermissionOnResourceType(UpdatePermissionOnResourceType),
UpdatePermissionOnTarget(UpdatePermissionOnTarget),
// ==== RESOURCE ====
UpdateResourceMeta(UpdateResourceMeta),
// ==== SWARM ====
CreateSwarm(CreateSwarm),
CopySwarm(CopySwarm),
DeleteSwarm(DeleteSwarm),
UpdateSwarm(UpdateSwarm),
RenameSwarm(RenameSwarm),
// ==== SERVER ====
CreateServer(CreateServer),
CopyServer(CopyServer),
@@ -93,6 +74,12 @@ pub enum WriteRequest {
UpdateServerPublicKey(UpdateServerPublicKey),
RotateServerKeys(RotateServerKeys),
// ==== TERMINAL ====
CreateTerminal(CreateTerminal),
DeleteTerminal(DeleteTerminal),
DeleteAllTerminals(DeleteAllTerminals),
BatchDeleteAllTerminals(BatchDeleteAllTerminals),
// ==== STACK ====
CreateStack(CreateStack),
CopyStack(CopyStack),
@@ -119,13 +106,6 @@ pub enum WriteRequest {
WriteBuildFileContents(WriteBuildFileContents),
RefreshBuildCache(RefreshBuildCache),
// ==== BUILDER ====
CreateBuilder(CreateBuilder),
CopyBuilder(CopyBuilder),
DeleteBuilder(DeleteBuilder),
UpdateBuilder(UpdateBuilder),
RenameBuilder(RenameBuilder),
// ==== REPO ====
CreateRepo(CreateRepo),
CopyRepo(CopyRepo),
@@ -134,13 +114,6 @@ pub enum WriteRequest {
RenameRepo(RenameRepo),
RefreshRepoCache(RefreshRepoCache),
// ==== ALERTER ====
CreateAlerter(CreateAlerter),
CopyAlerter(CopyAlerter),
DeleteAlerter(DeleteAlerter),
UpdateAlerter(UpdateAlerter),
RenameAlerter(RenameAlerter),
// ==== PROCEDURE ====
CreateProcedure(CreateProcedure),
CopyProcedure(CopyProcedure),
@@ -165,11 +138,51 @@ pub enum WriteRequest {
CommitSync(CommitSync),
RefreshResourceSyncPending(RefreshResourceSyncPending),
// ==== TERMINAL ====
CreateTerminal(CreateTerminal),
DeleteTerminal(DeleteTerminal),
DeleteAllTerminals(DeleteAllTerminals),
BatchDeleteAllTerminals(BatchDeleteAllTerminals),
// ==== BUILDER ====
CreateBuilder(CreateBuilder),
CopyBuilder(CopyBuilder),
DeleteBuilder(DeleteBuilder),
UpdateBuilder(UpdateBuilder),
RenameBuilder(RenameBuilder),
// ==== ALERTER ====
CreateAlerter(CreateAlerter),
CopyAlerter(CopyAlerter),
DeleteAlerter(DeleteAlerter),
UpdateAlerter(UpdateAlerter),
RenameAlerter(RenameAlerter),
// ==== ONBOARDING KEY ====
CreateOnboardingKey(CreateOnboardingKey),
UpdateOnboardingKey(UpdateOnboardingKey),
DeleteOnboardingKey(DeleteOnboardingKey),
// ==== USER ====
CreateLocalUser(CreateLocalUser),
UpdateUserUsername(UpdateUserUsername),
UpdateUserPassword(UpdateUserPassword),
DeleteUser(DeleteUser),
// ==== SERVICE USER ====
CreateServiceUser(CreateServiceUser),
UpdateServiceUserDescription(UpdateServiceUserDescription),
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
// ==== USER GROUP ====
CreateUserGroup(CreateUserGroup),
RenameUserGroup(RenameUserGroup),
DeleteUserGroup(DeleteUserGroup),
AddUserToUserGroup(AddUserToUserGroup),
RemoveUserFromUserGroup(RemoveUserFromUserGroup),
SetUsersInUserGroup(SetUsersInUserGroup),
SetEveryoneUserGroup(SetEveryoneUserGroup),
// ==== PERMISSIONS ====
UpdateUserAdmin(UpdateUserAdmin),
UpdateUserBasePermissions(UpdateUserBasePermissions),
UpdatePermissionOnResourceType(UpdatePermissionOnResourceType),
UpdatePermissionOnTarget(UpdatePermissionOnTarget),
// ==== TAG ====
CreateTag(CreateTag),
@@ -192,11 +205,6 @@ pub enum WriteRequest {
UpdateDockerRegistryAccount(UpdateDockerRegistryAccount),
DeleteDockerRegistryAccount(DeleteDockerRegistryAccount),
// ==== ONBOARDING KEY ====
CreateOnboardingKey(CreateOnboardingKey),
UpdateOnboardingKey(UpdateOnboardingKey),
DeleteOnboardingKey(DeleteOnboardingKey),
// ==== ALERT ====
CloseAlert(CloseAlert),
}
+80 -66
View File
@@ -307,8 +307,8 @@ async fn extract_user_target_with_validation(
.users
.find_one(filter)
.await
.context("failed to query db for users")?
.context("no matching user found")?
.context("Failed to query db for users")?
.context("No matching user found")?
.id;
Ok((UserTargetVariant::User, id))
}
@@ -321,8 +321,8 @@ async fn extract_user_target_with_validation(
.user_groups
.find_one(filter)
.await
.context("failed to query db for user_groups")?
.context("no matching user_group found")?
.context("Failed to query db for user_groups")?
.context("No matching user_group found")?
.id;
Ok((UserTargetVariant::UserGroup, id))
}
@@ -338,47 +338,19 @@ async fn extract_resource_target_with_validation(
let res = resource_target.extract_variant_id();
Ok((res.0, res.1.clone()))
}
ResourceTarget::Build(ident) => {
ResourceTarget::Swarm(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.builds
.swarms
.find_one(filter)
.await
.context("failed to query db for builds")?
.context("no matching build found")?
.context("Failed to query db for swarms")?
.context("No matching server found")?
.id;
Ok((ResourceTargetVariant::Build, id))
}
ResourceTarget::Builder(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.builders
.find_one(filter)
.await
.context("failed to query db for builders")?
.context("no matching builder found")?
.id;
Ok((ResourceTargetVariant::Builder, id))
}
ResourceTarget::Deployment(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.deployments
.find_one(filter)
.await
.context("failed to query db for deployments")?
.context("no matching deployment found")?
.id;
Ok((ResourceTargetVariant::Deployment, id))
Ok((ResourceTargetVariant::Server, id))
}
ResourceTarget::Server(ident) => {
let filter = match ObjectId::from_str(ident) {
@@ -389,11 +361,53 @@ async fn extract_resource_target_with_validation(
.servers
.find_one(filter)
.await
.context("failed to query db for servers")?
.context("no matching server found")?
.context("Failed to query db for servers")?
.context("No matching server found")?
.id;
Ok((ResourceTargetVariant::Server, id))
}
ResourceTarget::Stack(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.stacks
.find_one(filter)
.await
.context("Failed to query db for stacks")?
.context("No matching stack found")?
.id;
Ok((ResourceTargetVariant::Stack, id))
}
ResourceTarget::Deployment(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.deployments
.find_one(filter)
.await
.context("Failed to query db for deployments")?
.context("No matching deployment found")?
.id;
Ok((ResourceTargetVariant::Deployment, id))
}
ResourceTarget::Build(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.builds
.find_one(filter)
.await
.context("Failed to query db for builds")?
.context("No matching build found")?
.id;
Ok((ResourceTargetVariant::Build, id))
}
ResourceTarget::Repo(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
@@ -403,25 +417,11 @@ async fn extract_resource_target_with_validation(
.repos
.find_one(filter)
.await
.context("failed to query db for repos")?
.context("no matching repo found")?
.context("Failed to query db for repos")?
.context("No matching repo found")?
.id;
Ok((ResourceTargetVariant::Repo, id))
}
ResourceTarget::Alerter(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.alerters
.find_one(filter)
.await
.context("failed to query db for alerters")?
.context("no matching alerter found")?
.id;
Ok((ResourceTargetVariant::Alerter, id))
}
ResourceTarget::Procedure(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
@@ -431,8 +431,8 @@ async fn extract_resource_target_with_validation(
.procedures
.find_one(filter)
.await
.context("failed to query db for procedures")?
.context("no matching procedure found")?
.context("Failed to query db for procedures")?
.context("No matching procedure found")?
.id;
Ok((ResourceTargetVariant::Procedure, id))
}
@@ -445,8 +445,8 @@ async fn extract_resource_target_with_validation(
.actions
.find_one(filter)
.await
.context("failed to query db for actions")?
.context("no matching action found")?
.context("Failed to query db for actions")?
.context("No matching action found")?
.id;
Ok((ResourceTargetVariant::Action, id))
}
@@ -459,24 +459,38 @@ async fn extract_resource_target_with_validation(
.resource_syncs
.find_one(filter)
.await
.context("failed to query db for resource syncs")?
.context("no matching resource sync found")?
.context("Failed to query db for resource syncs")?
.context("No matching resource sync found")?
.id;
Ok((ResourceTargetVariant::ResourceSync, id))
}
ResourceTarget::Stack(ident) => {
ResourceTarget::Builder(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.stacks
.builders
.find_one(filter)
.await
.context("failed to query db for stacks")?
.context("no matching stack found")?
.context("Failed to query db for builders")?
.context("No matching builder found")?
.id;
Ok((ResourceTargetVariant::Stack, id))
Ok((ResourceTargetVariant::Builder, id))
}
ResourceTarget::Alerter(ident) => {
let filter = match ObjectId::from_str(ident) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": ident },
};
let id = db_client()
.alerters
.find_one(filter)
.await
.context("Failed to query db for alerters")?
.context("No matching alerter found")?
.id;
Ok((ResourceTargetVariant::Alerter, id))
}
}
}
+13 -9
View File
@@ -5,7 +5,8 @@ use komodo_client::{
entities::{
ResourceTarget, action::Action, alerter::Alerter, build::Build,
builder::Builder, deployment::Deployment, procedure::Procedure,
repo::Repo, server::Server, stack::Stack, sync::ResourceSync,
repo::Repo, server::Server, stack::Stack, swarm::Swarm,
sync::ResourceSync,
},
};
use reqwest::StatusCode;
@@ -45,9 +46,15 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
.status_code(StatusCode::BAD_REQUEST),
);
}
ResourceTarget::Swarm(id) => {
resource::update_meta::<Swarm>(&id, meta, args).await?;
}
ResourceTarget::Server(id) => {
resource::update_meta::<Server>(&id, meta, args).await?;
}
ResourceTarget::Stack(id) => {
resource::update_meta::<Stack>(&id, meta, args).await?;
}
ResourceTarget::Deployment(id) => {
resource::update_meta::<Deployment>(&id, meta, args).await?;
}
@@ -57,12 +64,6 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
ResourceTarget::Repo(id) => {
resource::update_meta::<Repo>(&id, meta, args).await?;
}
ResourceTarget::Builder(id) => {
resource::update_meta::<Builder>(&id, meta, args).await?;
}
ResourceTarget::Alerter(id) => {
resource::update_meta::<Alerter>(&id, meta, args).await?;
}
ResourceTarget::Procedure(id) => {
resource::update_meta::<Procedure>(&id, meta, args).await?;
}
@@ -73,8 +74,11 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
resource::update_meta::<ResourceSync>(&id, meta, args)
.await?;
}
ResourceTarget::Stack(id) => {
resource::update_meta::<Stack>(&id, meta, args).await?;
ResourceTarget::Builder(id) => {
resource::update_meta::<Builder>(&id, meta, args).await?;
}
ResourceTarget::Alerter(id) => {
resource::update_meta::<Alerter>(&id, meta, args).await?;
}
}
Ok(UpdateResourceMetaResponse {})
+108
View File
@@ -0,0 +1,108 @@
use komodo_client::{
api::write::*,
entities::{
permission::PermissionLevel, swarm::Swarm, update::Update,
},
};
use resolver_api::Resolve;
use crate::{permission::get_check_permissions, resource};
use super::WriteArgs;
impl Resolve<WriteArgs> for CreateSwarm {
#[instrument(
"CreateSwarm",
skip_all,
fields(
operator = user.id,
swarm = self.name,
config = serde_json::to_string(&self.config).unwrap(),
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
resource::create::<Swarm>(&self.name, self.config, None, user)
.await
}
}
impl Resolve<WriteArgs> for CopySwarm {
#[instrument(
"CopySwarm",
skip_all,
fields(
operator = user.id,
swarm = self.name,
copy_swarm = self.id,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
let Swarm { config, .. } = get_check_permissions::<Swarm>(
&self.id,
user,
PermissionLevel::Read.into(),
)
.await?;
resource::create::<Swarm>(&self.name, config.into(), None, user)
.await
}
}
impl Resolve<WriteArgs> for DeleteSwarm {
#[instrument(
"DeleteSwarm",
skip_all,
fields(
operator = user.id,
swarm = self.id,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
Ok(resource::delete::<Swarm>(&self.id, user).await?)
}
}
impl Resolve<WriteArgs> for UpdateSwarm {
#[instrument(
"UpdateSwarm",
skip_all,
fields(
operator = user.id,
swarm = self.id,
update = serde_json::to_string(&self.config).unwrap()
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
Ok(resource::update::<Swarm>(&self.id, self.config, user).await?)
}
}
impl Resolve<WriteArgs> for RenameSwarm {
#[instrument(
"RenameSwarm",
skip_all,
fields(
operator = user.id,
swarm = self.id,
new_name = self.name
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
Ok(resource::rename::<Swarm>(&self.id, &self.name, user).await?)
}
}
+1 -6
View File
@@ -45,7 +45,6 @@ impl PeripheryConnectionArgs<'_> {
periphery_connections().insert(id.clone(), self).await;
let responses = connection.responses.clone();
let terminals = connection.terminals.clone();
tokio::spawn(async move {
loop {
@@ -91,11 +90,7 @@ impl PeripheryConnectionArgs<'_> {
}
});
Ok(PeripheryClient {
id,
responses,
terminals,
})
Ok(PeripheryClient { id, responses })
}
}
+2 -1
View File
@@ -9,12 +9,13 @@ use komodo_client::{
deployment::DeploymentActionState,
procedure::ProcedureActionState, repo::RepoActionState,
server::ServerActionState, stack::StackActionState,
sync::ResourceSyncActionState,
swarm::SwarmActionState, sync::ResourceSyncActionState,
},
};
#[derive(Default)]
pub struct ActionStates {
pub swarm: CloneCache<String, Arc<ActionState<SwarmActionState>>>,
pub server: CloneCache<String, Arc<ActionState<ServerActionState>>>,
pub stack: CloneCache<String, Arc<ActionState<StackActionState>>>,
pub deployment:
+6 -1
View File
@@ -3,11 +3,12 @@ use std::collections::HashMap;
use komodo_client::entities::{
action::Action, alerter::Alerter, build::Build, builder::Builder,
deployment::Deployment, procedure::Procedure, repo::Repo,
server::Server, stack::Stack, sync::ResourceSync,
server::Server, stack::Stack, swarm::Swarm, sync::ResourceSync,
};
#[derive(Debug, Default)]
pub struct AllResourcesById {
pub swarms: HashMap<String, Swarm>,
pub servers: HashMap<String, Server>,
pub deployments: HashMap<String, Deployment>,
pub stacks: HashMap<String, Stack>,
@@ -27,6 +28,10 @@ impl AllResourcesById {
let id_to_tags = &map;
let match_tags = &[];
Ok(Self {
swarms: crate::resource::get_id_to_resource_map::<Swarm>(
id_to_tags, match_tags,
)
.await?,
servers: crate::resource::get_id_to_resource_map::<Server>(
id_to_tags, match_tags,
)
+1
View File
@@ -30,6 +30,7 @@ pub mod matcher;
pub mod procedure;
pub mod prune;
pub mod query;
pub mod swarm;
pub mod terminal;
pub mod update;
+17 -13
View File
@@ -25,6 +25,7 @@ use komodo_client::{
repo::Repo,
server::{Server, ServerState},
stack::{Stack, StackServiceNames, StackState},
swarm::Swarm,
sync::ResourceSync,
tag::Tag,
update::Update,
@@ -287,24 +288,24 @@ pub async fn get_user_permission_on_target(
) -> anyhow::Result<PermissionLevelAndSpecifics> {
match target {
ResourceTarget::System(_) => Ok(PermissionLevel::None.into()),
ResourceTarget::Build(id) => {
get_user_permission_on_resource::<Build>(user, id).await
}
ResourceTarget::Builder(id) => {
get_user_permission_on_resource::<Builder>(user, id).await
}
ResourceTarget::Deployment(id) => {
get_user_permission_on_resource::<Deployment>(user, id).await
ResourceTarget::Swarm(id) => {
get_user_permission_on_resource::<Swarm>(user, id).await
}
ResourceTarget::Server(id) => {
get_user_permission_on_resource::<Server>(user, id).await
}
ResourceTarget::Stack(id) => {
get_user_permission_on_resource::<Stack>(user, id).await
}
ResourceTarget::Deployment(id) => {
get_user_permission_on_resource::<Deployment>(user, id).await
}
ResourceTarget::Build(id) => {
get_user_permission_on_resource::<Build>(user, id).await
}
ResourceTarget::Repo(id) => {
get_user_permission_on_resource::<Repo>(user, id).await
}
ResourceTarget::Alerter(id) => {
get_user_permission_on_resource::<Alerter>(user, id).await
}
ResourceTarget::Procedure(id) => {
get_user_permission_on_resource::<Procedure>(user, id).await
}
@@ -314,8 +315,11 @@ pub async fn get_user_permission_on_target(
ResourceTarget::ResourceSync(id) => {
get_user_permission_on_resource::<ResourceSync>(user, id).await
}
ResourceTarget::Stack(id) => {
get_user_permission_on_resource::<Stack>(user, id).await
ResourceTarget::Builder(id) => {
get_user_permission_on_resource::<Builder>(user, id).await
}
ResourceTarget::Alerter(id) => {
get_user_permission_on_resource::<Alerter>(user, id).await
}
}
}
+70
View File
@@ -0,0 +1,70 @@
use std::time::Duration;
use anyhow::anyhow;
use komodo_client::entities::server::{Server, ServerState};
use resolver_api::HasResponse;
use serde::{Serialize, de::DeserializeOwned};
use crate::{
helpers::periphery_client, resource, state::server_status_cache,
};
pub async fn swarm_request<T>(
server_ids: &[String],
request: T,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Clone + Serialize + HasResponse,
T::Response: DeserializeOwned,
{
swarm_request_custom_timeout(
server_ids,
request,
Duration::from_secs(10),
)
.await
}
pub async fn swarm_request_custom_timeout<T>(
server_ids: &[String],
request: T,
timeout: Duration,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Clone + Serialize + HasResponse,
T::Response: DeserializeOwned,
{
let status_cache = server_status_cache();
let mut err = Option::<anyhow::Error>::None;
for server_id in server_ids {
let Some(ServerState::Ok) =
status_cache.get(server_id).await.map(|status| status.state)
else {
err = Some(anyhow!("No managers connected"));
continue;
};
let Ok(periphery) =
(match resource::get::<Server>(server_id).await {
Ok(server) => periphery_client(&server).await,
Err(e) => {
err = Some(e);
continue;
}
})
else {
continue;
};
match periphery
.request_custom_timeout(request.clone(), timeout)
.await
{
Ok(res) => return Ok(res),
Err(e) => err = Some(e),
}
}
Err(
err
.unwrap_or_else(|| anyhow!("Unknown error"))
.context("Failed to request from Swarm"),
)
}
+1 -1
View File
@@ -76,7 +76,7 @@ async fn app() -> anyhow::Result<()> {
startup::on_startup().await;
// Spawn background tasks
monitor::spawn_monitor_loop();
monitor::spawn_monitoring_loops();
resource::spawn_resource_refresh_loop();
resource::spawn_all_resources_cache_refresh_loop();
resource::spawn_build_state_refresh_loop();
+55 -74
View File
@@ -1,29 +1,73 @@
use komodo_client::entities::{
alert::SeverityLevel,
deployment::{Deployment, DeploymentState},
docker::{
container::ContainerListItem, image::ImageListItem,
network::NetworkListItem, volume::VolumeListItem,
},
docker::DockerLists,
repo::Repo,
server::{
PeripheryInformation, Server, ServerConfig, ServerHealth,
ServerHealthState, ServerState,
},
stack::{ComposeProject, Stack, StackState},
stack::{Stack, StackState},
stats::{SingleDiskUsage, SystemInformation, SystemStats},
};
use serror::Serror;
use crate::state::{
deployment_status_cache, repo_status_cache, server_status_cache,
stack_status_cache,
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
CachedStackStatus, History, deployment_status_cache,
repo_status_cache, server_status_cache, stack_status_cache,
};
use super::{
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
CachedStackStatus, History,
};
pub async fn insert_server_status(
server: &Server,
state: ServerState,
periphery_info: Option<PeripheryInformation>,
system_info: Option<SystemInformation>,
system_stats: Option<SystemStats>,
docker: Option<DockerLists>,
err: impl Into<Option<Serror>>,
) {
let health =
system_stats.as_ref().map(|s| get_server_health(server, s));
server_status_cache()
.insert(
server.id.clone(),
CachedServerStatus {
id: server.id.clone(),
state,
periphery_info,
system_info,
system_stats,
health,
docker,
err: err.into(),
}
.into(),
)
.await;
}
pub async fn insert_stacks_status_unknown(stacks: Vec<Stack>) {
let status_cache = stack_status_cache();
for stack in stacks {
let prev =
status_cache.get(&stack.id).await.map(|s| s.curr.state);
status_cache
.insert(
stack.id.clone(),
History {
curr: CachedStackStatus {
id: stack.id,
state: StackState::Unknown,
services: Vec::new(),
},
prev,
}
.into(),
)
.await;
}
}
pub async fn insert_deployments_status_unknown(
deployments: Vec<Deployment>,
@@ -66,69 +110,6 @@ pub async fn insert_repos_status_unknown(repos: Vec<Repo>) {
}
}
pub async fn insert_stacks_status_unknown(stacks: Vec<Stack>) {
let status_cache = stack_status_cache();
for stack in stacks {
let prev =
status_cache.get(&stack.id).await.map(|s| s.curr.state);
status_cache
.insert(
stack.id.clone(),
History {
curr: CachedStackStatus {
id: stack.id,
state: StackState::Unknown,
services: Vec::new(),
},
prev,
}
.into(),
)
.await;
}
}
type DockerLists = (
Option<Vec<ContainerListItem>>,
Option<Vec<NetworkListItem>>,
Option<Vec<ImageListItem>>,
Option<Vec<VolumeListItem>>,
Option<Vec<ComposeProject>>,
);
pub async fn insert_server_status(
server: &Server,
state: ServerState,
periphery_info: Option<PeripheryInformation>,
system_info: Option<SystemInformation>,
system_stats: Option<SystemStats>,
(containers, networks, images, volumes, projects): DockerLists,
err: impl Into<Option<Serror>>,
) {
let health =
system_stats.as_ref().map(|s| get_server_health(server, s));
server_status_cache()
.insert(
server.id.clone(),
CachedServerStatus {
id: server.id.clone(),
state,
periphery_info,
system_info,
system_stats,
health,
containers,
networks,
images,
volumes,
projects,
err: err.into(),
}
.into(),
)
.await;
}
const ALERT_PERCENTAGE_THRESHOLD: f32 = 5.0;
fn get_server_health(
+46 -84
View File
@@ -7,19 +7,15 @@ use futures_util::future::join_all;
use helpers::insert_stacks_status_unknown;
use komodo_client::entities::{
build::Build,
deployment::{Deployment, DeploymentState},
docker::{
container::ContainerListItem, image::ImageListItem,
network::NetworkListItem, volume::VolumeListItem,
},
deployment::Deployment,
komodo_timestamp, optional_string,
repo::Repo,
server::{PeripheryInformation, Server, ServerHealth, ServerState},
stack::{ComposeProject, Stack, StackService, StackState},
stats::{SystemInformation, SystemStats},
server::{Server, ServerState},
stack::Stack,
stats::SystemStats,
};
use periphery_client::api::{
self, PollStatusResponse, git::GetLatestCommit,
self, git::GetLatestCommit, poll::PollStatusResponse,
};
use serror::Serror;
use tokio::sync::Mutex;
@@ -29,8 +25,8 @@ use crate::{
helpers::periphery_client,
monitor::{alert::check_alerts, record::record_server_stats},
state::{
db_client, deployment_status_cache, periphery_connections,
repo_status_cache,
CachedRepoStatus, db_client, deployment_status_cache,
periphery_connections, repo_status_cache,
},
};
@@ -43,62 +39,24 @@ mod alert;
mod helpers;
mod record;
mod resources;
mod swarm;
#[derive(Default, Debug)]
pub struct History<Curr: Default, Prev> {
pub curr: Curr,
pub prev: Option<Prev>,
}
#[derive(Default, Clone, Debug)]
pub struct CachedServerStatus {
pub id: String,
pub state: ServerState,
pub health: Option<ServerHealth>,
pub periphery_info: Option<PeripheryInformation>,
pub system_info: Option<SystemInformation>,
pub system_stats: Option<SystemStats>,
pub containers: Option<Vec<ContainerListItem>>,
pub networks: Option<Vec<NetworkListItem>>,
pub images: Option<Vec<ImageListItem>>,
pub volumes: Option<Vec<VolumeListItem>>,
pub projects: Option<Vec<ComposeProject>>,
/// Store the error in reaching periphery
pub err: Option<serror::Serror>,
}
#[derive(Default, Clone, Debug)]
pub struct CachedDeploymentStatus {
/// The deployment id
pub id: String,
pub state: DeploymentState,
pub container: Option<ContainerListItem>,
pub update_available: bool,
}
#[derive(Default, Clone, Debug)]
pub struct CachedRepoStatus {
pub latest_hash: Option<String>,
pub latest_message: Option<String>,
}
#[derive(Default, Clone, Debug)]
pub struct CachedStackStatus {
/// The stack id
pub id: String,
/// The stack state
pub state: StackState,
/// The services connected to the stack
pub services: Vec<StackService>,
}
pub use swarm::update_cache_for_swarm;
const ADDITIONAL_MS: u128 = 500;
pub fn spawn_monitor_loop() {
let interval: async_timing_util::Timelength = core_config()
pub fn spawn_monitoring_loops() {
let interval = core_config()
.monitoring_interval
.try_into()
.expect("Invalid monitoring interval");
spawn_server_monitoring_loop(interval);
swarm::spawn_swarm_monitoring_loop(interval);
}
fn spawn_server_monitoring_loop(
interval: async_timing_util::Timelength,
) {
tokio::spawn(async move {
refresh_server_cache(komodo_timestamp()).await;
loop {
@@ -115,7 +73,7 @@ async fn refresh_server_cache(ts: i64) {
Ok(servers) => servers,
Err(e) => {
error!(
"failed to get server list (manage status cache) | {e:#}"
"Failed to get server list (refresh server cache) | {e:#}"
);
return;
}
@@ -172,7 +130,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
None,
None,
None,
(None, None, None, None, None),
None,
None,
)
.await;
@@ -190,7 +148,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
None,
None,
None,
(None, None, None, None, None),
None,
Serror::from(&e),
)
.await;
@@ -202,14 +160,11 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
periphery_info,
system_info,
system_stats,
mut containers,
networks,
images,
volumes,
projects,
mut docker,
} = match periphery
.request(api::PollStatus {
.request(api::poll::PollStatus {
include_stats: server.config.stats_monitoring,
include_docker: true,
})
.await
{
@@ -222,7 +177,7 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
None,
None,
None,
(None, None, None, None, None),
None,
Serror::from(&e),
)
.await;
@@ -230,37 +185,44 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
}
};
containers.iter_mut().for_each(|container| {
container.server_id = Some(server.id.clone())
});
if let Some(docker) = &mut docker {
docker.containers.iter_mut().for_each(|container| {
container.server_id = Some(server.id.clone())
});
}
let containers = docker
.as_ref()
.map(|docker| docker.containers.as_slice())
.unwrap_or(&[]);
let images = docker
.as_ref()
.map(|docker| docker.images.as_slice())
.unwrap_or(&[]);
tokio::join!(
resources::update_deployment_cache(
server.name.clone(),
resources.deployments,
&containers,
&images,
containers,
images,
&resources.builds,
),
resources::update_stack_cache(
server.name.clone(),
resources.stacks,
&containers,
&images
containers,
images
),
);
insert_server_status(
server,
ServerState::Ok,
Some(periphery_info),
Some(system_info),
system_stats.map(|stats| filter_volumes(server, stats)),
(
Some(containers.clone()),
Some(networks),
Some(images),
Some(volumes),
Some(projects),
),
docker,
None,
)
.await;
+188 -189
View File
@@ -30,200 +30,12 @@ use crate::{
services::extract_services_from_stack,
},
state::{
CachedDeploymentStatus, CachedStackStatus, History,
action_states, db_client, deployment_status_cache,
stack_status_cache,
},
};
use super::{CachedDeploymentStatus, CachedStackStatus, History};
fn deployment_alert_sent_cache() -> &'static Mutex<HashSet<String>> {
static CACHE: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
CACHE.get_or_init(Default::default)
}
pub async fn update_deployment_cache(
server_name: String,
deployments: Vec<Deployment>,
containers: &[ContainerListItem],
images: &[ImageListItem],
builds: &[Build],
) {
let deployment_status_cache = deployment_status_cache();
for deployment in deployments {
let container = containers
.iter()
.find(|container| container.name == deployment.name)
.cloned();
let prev = deployment_status_cache
.get(&deployment.id)
.await
.map(|s| s.curr.state);
let state = container
.as_ref()
.map(|c| c.state.into())
.unwrap_or(DeploymentState::NotDeployed);
let image = match deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let (build_name, build_version) = builds
.iter()
.find(|build| build.id == build_id)
.map(|b| (b.name.as_ref(), b.config.version))
.unwrap_or(("Unknown", Default::default()));
let version = if version.is_none() {
build_version.to_string()
} else {
version.to_string()
};
format!("{build_name}:{version}")
}
DeploymentImage::Image { image } => {
// If image already has tag, leave it,
// otherwise default the tag to latest
if image.contains(':') {
image.to_string()
} else {
format!("{image}:latest")
}
}
};
let update_available = if let Some(ContainerListItem {
image_id: Some(curr_image_id),
..
}) = &container
{
// Docker will automatically strip `docker.io` from incoming image names re #468.
// Need to strip it in order to match by image name and find available updates.
let image = image.strip_prefix("docker.io/").unwrap_or(&image);
images
.iter()
.find(|i| i.name == image)
.map(|i| &i.id != curr_image_id)
.unwrap_or_default()
} else {
false
};
if update_available {
if deployment.config.auto_update {
if state == DeploymentState::Running
&& !action_states()
.deployment
.get_or_insert_default(&deployment.id)
.await
.busy()
.unwrap_or(true)
{
let id = deployment.id.clone();
let server_name = server_name.clone();
tokio::spawn(async move {
match execute::inner_handler(
ExecuteRequest::Deploy(Deploy {
deployment: deployment.name.clone(),
stop_time: None,
stop_signal: None,
}),
auto_redeploy_user().to_owned(),
)
.await
{
Ok(_) => {
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(id.clone()),
data: AlertData::DeploymentAutoUpdated {
id,
name: deployment.name,
server_name,
server_id: deployment.config.server_id,
image,
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentAutoUpdated to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
Err(e) => {
warn!(
"Failed to auto update Deployment {} | {e:#}",
deployment.name
)
}
}
});
}
} else if state == DeploymentState::Running
&& deployment.config.send_alerts
&& !deployment_alert_sent_cache()
.lock()
.unwrap()
.contains(&deployment.id)
{
// Add that it is already sent to the cache, so another alert won't be sent.
deployment_alert_sent_cache()
.lock()
.unwrap()
.insert(deployment.id.clone());
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(deployment.id.clone()),
data: AlertData::DeploymentImageUpdateAvailable {
id: deployment.id.clone(),
name: deployment.name,
server_name: server_name.clone(),
server_id: deployment.config.server_id,
image,
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentImageUpdateAvailable to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
} else {
// If it sees there is no longer update available, remove
// from the sent cache, so on next `update_available = true`
// the cache is empty and a fresh alert will be sent.
deployment_alert_sent_cache()
.lock()
.unwrap()
.remove(&deployment.id);
}
deployment_status_cache
.insert(
deployment.id.clone(),
History {
curr: CachedDeploymentStatus {
id: deployment.id,
state,
container,
update_available,
},
prev,
}
.into(),
)
.await;
}
}
/// (StackId, Service)
fn stack_alert_sent_cache()
-> &'static Mutex<HashSet<(String, String)>> {
@@ -425,3 +237,190 @@ pub async fn update_stack_cache(
.await;
}
}
fn deployment_alert_sent_cache() -> &'static Mutex<HashSet<String>> {
static CACHE: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
CACHE.get_or_init(Default::default)
}
pub async fn update_deployment_cache(
server_name: String,
deployments: Vec<Deployment>,
containers: &[ContainerListItem],
images: &[ImageListItem],
builds: &[Build],
) {
let deployment_status_cache = deployment_status_cache();
for deployment in deployments {
let container = containers
.iter()
.find(|container| container.name == deployment.name)
.cloned();
let prev = deployment_status_cache
.get(&deployment.id)
.await
.map(|s| s.curr.state);
let state = container
.as_ref()
.map(|c| c.state.into())
.unwrap_or(DeploymentState::NotDeployed);
let image = match deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let (build_name, build_version) = builds
.iter()
.find(|build| build.id == build_id)
.map(|b| (b.name.as_ref(), b.config.version))
.unwrap_or(("Unknown", Default::default()));
let version = if version.is_none() {
build_version.to_string()
} else {
version.to_string()
};
format!("{build_name}:{version}")
}
DeploymentImage::Image { image } => {
// If image already has tag, leave it,
// otherwise default the tag to latest
if image.contains(':') {
image.to_string()
} else {
format!("{image}:latest")
}
}
};
let update_available = if let Some(ContainerListItem {
image_id: Some(curr_image_id),
..
}) = &container
{
// Docker will automatically strip `docker.io` from incoming image names re #468.
// Need to strip it in order to match by image name and find available updates.
let image = image.strip_prefix("docker.io/").unwrap_or(&image);
images
.iter()
.find(|i| i.name == image)
.map(|i| &i.id != curr_image_id)
.unwrap_or_default()
} else {
false
};
if update_available {
if deployment.config.auto_update {
if state == DeploymentState::Running
&& !action_states()
.deployment
.get_or_insert_default(&deployment.id)
.await
.busy()
.unwrap_or(true)
{
let id = deployment.id.clone();
let server_name = server_name.clone();
tokio::spawn(async move {
match execute::inner_handler(
ExecuteRequest::Deploy(Deploy {
deployment: deployment.name.clone(),
stop_time: None,
stop_signal: None,
}),
auto_redeploy_user().to_owned(),
)
.await
{
Ok(_) => {
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(id.clone()),
data: AlertData::DeploymentAutoUpdated {
id,
name: deployment.name,
server_name,
server_id: deployment.config.server_id,
image,
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentAutoUpdated to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
Err(e) => {
warn!(
"Failed to auto update Deployment {} | {e:#}",
deployment.name
)
}
}
});
}
} else if state == DeploymentState::Running
&& deployment.config.send_alerts
&& !deployment_alert_sent_cache()
.lock()
.unwrap()
.contains(&deployment.id)
{
// Add that it is already sent to the cache, so another alert won't be sent.
deployment_alert_sent_cache()
.lock()
.unwrap()
.insert(deployment.id.clone());
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(deployment.id.clone()),
data: AlertData::DeploymentImageUpdateAvailable {
id: deployment.id.clone(),
name: deployment.name,
server_name: server_name.clone(),
server_id: deployment.config.server_id,
image,
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentImageUpdateAvailable to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
} else {
// If it sees there is no longer update available, remove
// from the sent cache, so on next `update_available = true`
// the cache is empty and a fresh alert will be sent.
deployment_alert_sent_cache()
.lock()
.unwrap()
.remove(&deployment.id);
}
deployment_status_cache
.insert(
deployment.id.clone(),
History {
curr: CachedDeploymentStatus {
id: deployment.id,
state,
container,
update_available,
},
prev,
}
.into(),
)
.await;
}
}
+158
View File
@@ -0,0 +1,158 @@
use std::{
sync::{Arc, OnceLock},
time::Duration,
};
use anyhow::anyhow;
use async_timing_util::wait_until_timelength;
use cache::CloneCache;
use database::mungos::find::find_collect;
use formatting::format_serror;
use futures_util::future::join_all;
use komodo_client::entities::{
docker::node::NodeState,
komodo_timestamp,
swarm::{Swarm, SwarmState},
};
use periphery_client::api::swarm::{
PollSwarmStatus, PollSwarmStatusResponse,
};
use tokio::sync::Mutex;
use crate::{
helpers::swarm::swarm_request_custom_timeout,
state::{CachedSwarmStatus, db_client, swarm_status_cache},
};
const ADDITIONAL_MS: u128 = 1000;
pub fn spawn_swarm_monitoring_loop(
interval: async_timing_util::Timelength,
) {
tokio::spawn(async move {
refresh_swarm_cache(komodo_timestamp()).await;
loop {
let ts = (wait_until_timelength(interval, ADDITIONAL_MS).await
- ADDITIONAL_MS) as i64;
refresh_swarm_cache(ts).await;
}
});
}
async fn refresh_swarm_cache(_ts: i64) {
let swarms =
match find_collect(&db_client().swarms, None, None).await {
Ok(swarms) => swarms,
Err(e) => {
error!(
"Failed to get swarm list (refresh swarm cache) | {e:#}"
);
return;
}
};
let futures = swarms.into_iter().map(|swarm| async move {
update_cache_for_swarm(&swarm, false).await;
});
join_all(futures).await;
// tokio::join!(check_alerts(ts), record_swarm_stats(ts));
}
/// Makes sure cache for swarm doesn't update too frequently / simultaneously.
/// If forced, will still block against simultaneous update.
fn update_cache_for_swarm_controller()
-> &'static CloneCache<String, Arc<Mutex<i64>>> {
static CACHE: OnceLock<CloneCache<String, Arc<Mutex<i64>>>> =
OnceLock::new();
CACHE.get_or_init(Default::default)
}
/// The background loop will call this with force: false,
/// which exits early if the lock is busy or it was completed too recently.
/// If force is true, it will wait on simultaneous calls, and will
/// ignore the restriction on being completed too recently.
pub async fn update_cache_for_swarm(swarm: &Swarm, force: bool) {
// Concurrency controller to ensure it isn't done too often
// when it happens in other contexts.
let controller = update_cache_for_swarm_controller()
.get_or_insert_default(&swarm.id)
.await;
let mut lock = match controller.try_lock() {
Ok(lock) => lock,
Err(_) if force => controller.lock().await,
Err(_) => return,
};
let now = komodo_timestamp();
// early return if called again sooner than 1s.
if !force && *lock > now - 1_000 {
return;
}
*lock = now;
if swarm.config.server_ids.is_empty() {
swarm_status_cache()
.insert(
swarm.id.clone(),
CachedSwarmStatus {
state: SwarmState::Unknown,
inspect: None,
lists: None,
err: Some(format_serror(
&anyhow!("No Servers configured as manager nodes").into(),
)),
}
.into(),
)
.await;
return;
}
let PollSwarmStatusResponse { inspect, lists } =
match swarm_request_custom_timeout(
&swarm.config.server_ids,
PollSwarmStatus {},
Duration::from_secs(1),
)
.await
{
Ok(info) => info,
Err(e) => {
swarm_status_cache()
.insert(
swarm.id.clone(),
CachedSwarmStatus {
state: SwarmState::Unknown,
inspect: None,
lists: None,
err: Some(format_serror(&e.into())),
}
.into(),
)
.await;
return;
}
};
let mut state = SwarmState::Healthy;
for node in &lists.nodes {
if !matches!(node.state, Some(NodeState::READY)) {
state = SwarmState::Unhealthy;
}
}
swarm_status_cache()
.insert(
swarm.id.clone(),
CachedSwarmStatus {
state,
inspect,
lists: Some(lists),
err: None,
}
.into(),
)
.await;
}
+31 -17
View File
@@ -12,7 +12,6 @@ use uuid::Uuid;
use crate::{
connection::{
PeripheryConnection, PeripheryConnectionArgs, ResponseChannels,
TerminalChannels,
},
state::periphery_connections,
};
@@ -24,7 +23,6 @@ pub struct PeripheryClient {
/// Usually the server id
pub id: String,
pub responses: Arc<ResponseChannels>,
pub terminals: Arc<TerminalChannels>,
}
impl PeripheryClient {
@@ -51,7 +49,6 @@ impl PeripheryClient {
return Ok(PeripheryClient {
id,
responses: connection.responses.clone(),
terminals: connection.terminals.clone(),
});
}
@@ -68,7 +65,6 @@ impl PeripheryClient {
Ok(PeripheryClient {
id,
responses: connection.responses.clone(),
terminals: connection.terminals.clone(),
})
} else {
// Core -> Periphery connection
@@ -89,6 +85,20 @@ impl PeripheryClient {
&self,
request: T,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Serialize + HasResponse,
T::Response: DeserializeOwned,
{
self
.request_custom_timeout(request, Duration::from_secs(10))
.await
}
pub async fn request_custom_timeout<T>(
&self,
request: T,
timeout: Duration,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Serialize + HasResponse,
T::Response: DeserializeOwned,
@@ -117,24 +127,28 @@ impl PeripheryClient {
.await
.context("Failed to send request over channel")
{
// cleanup
self.terminals.remove(&channel_id).await;
self.responses.remove(&channel_id).await;
return Err(e);
}
// Poll for the associated response
loop {
let message = response_receiever
.recv()
.with_timeout(Duration::from_secs(10))
.await?;
let res = async {
// Poll for the associated response
loop {
let message =
response_receiever.recv().with_timeout(timeout).await?;
// Still in progress, sent to avoid timeout.
let Some(message) = message.decode()? else {
continue;
};
// Still in progress, sent to avoid timeout.
let Some(message) = message.decode()? else {
continue;
};
return message.decode();
return message.decode();
}
}
.await;
self.responses.remove(&channel_id).await;
res
}
}
+11 -8
View File
@@ -60,6 +60,7 @@ mod refresh;
mod repo;
mod server;
mod stack;
mod swarm;
mod sync;
pub use action::{
@@ -647,20 +648,21 @@ pub async fn update<T: KomodoResource>(
fn resource_target<T: KomodoResource>(id: String) -> ResourceTarget {
match T::resource_type() {
ResourceTargetVariant::System => ResourceTarget::System(id),
ResourceTargetVariant::Build => ResourceTarget::Build(id),
ResourceTargetVariant::Builder => ResourceTarget::Builder(id),
ResourceTargetVariant::Swarm => ResourceTarget::Swarm(id),
ResourceTargetVariant::Server => ResourceTarget::Server(id),
ResourceTargetVariant::Stack => ResourceTarget::Stack(id),
ResourceTargetVariant::Deployment => {
ResourceTarget::Deployment(id)
}
ResourceTargetVariant::Server => ResourceTarget::Server(id),
ResourceTargetVariant::Build => ResourceTarget::Build(id),
ResourceTargetVariant::Repo => ResourceTarget::Repo(id),
ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id),
ResourceTargetVariant::Procedure => ResourceTarget::Procedure(id),
ResourceTargetVariant::Action => ResourceTarget::Action(id),
ResourceTargetVariant::ResourceSync => {
ResourceTarget::ResourceSync(id)
}
ResourceTargetVariant::Stack => ResourceTarget::Stack(id),
ResourceTargetVariant::Action => ResourceTarget::Action(id),
ResourceTargetVariant::Builder => ResourceTarget::Builder(id),
ResourceTargetVariant::Alerter => ResourceTarget::Alerter(id),
}
}
@@ -929,16 +931,17 @@ where
{
let resource: ResourceTarget = resource.into();
let (recent_field, id) = match resource {
ResourceTarget::Swarm(id) => ("recents.Swarm", id),
ResourceTarget::Server(id) => ("recents.Server", id),
ResourceTarget::Stack(id) => ("recents.Stack", id),
ResourceTarget::Deployment(id) => ("recents.Deployment", id),
ResourceTarget::Build(id) => ("recents.Build", id),
ResourceTarget::Repo(id) => ("recents.Repo", id),
ResourceTarget::Procedure(id) => ("recents.Procedure", id),
ResourceTarget::Action(id) => ("recents.Action", id),
ResourceTarget::Stack(id) => ("recents.Stack", id),
ResourceTarget::ResourceSync(id) => ("recents.ResourceSync", id),
ResourceTarget::Builder(id) => ("recents.Builder", id),
ResourceTarget::Alerter(id) => ("recents.Alerter", id),
ResourceTarget::ResourceSync(id) => ("recents.ResourceSync", id),
ResourceTarget::System(_) => return,
};
if let Err(e) = db_client()
+3 -2
View File
@@ -147,8 +147,9 @@ impl super::KomodoResource for Stack {
.await
.as_ref()
{
if let Some(projects) = &status.projects {
if let Some(project) = projects
if let Some(docker) = &status.docker {
if let Some(project) = docker
.projects
.iter()
.find(|project| project.name == project_name)
{
+195
View File
@@ -0,0 +1,195 @@
use anyhow::Context;
use database::mungos::mongodb::Collection;
use komodo_client::entities::{
Operation, ResourceTarget, ResourceTargetVariant,
permission::PermissionLevel,
resource::Resource,
server::Server,
swarm::{
PartialSwarmConfig, Swarm, SwarmConfig, SwarmConfigDiff,
SwarmInfo, SwarmListItem, SwarmListItemInfo, SwarmQuerySpecifics,
},
update::Update,
user::User,
};
use crate::{
config::core_config,
monitor::update_cache_for_swarm,
state::{db_client, swarm_status_cache},
};
use super::get_check_permissions;
impl super::KomodoResource for Swarm {
type Config = SwarmConfig;
type PartialConfig = PartialSwarmConfig;
type ConfigDiff = SwarmConfigDiff;
type Info = SwarmInfo;
type ListItem = SwarmListItem;
type QuerySpecifics = SwarmQuerySpecifics;
fn resource_type() -> ResourceTargetVariant {
ResourceTargetVariant::Swarm
}
fn resource_target(id: impl Into<String>) -> ResourceTarget {
ResourceTarget::Swarm(id.into())
}
fn coll() -> &'static Collection<Resource<Self::Config, Self::Info>>
{
&db_client().swarms
}
async fn to_list_item(
swarm: Resource<Self::Config, Self::Info>,
) -> Self::ListItem {
let (state, err) = swarm_status_cache()
.get(&swarm.id)
.await
.map(|status| (status.state, status.err.clone()))
.unwrap_or_default();
SwarmListItem {
name: swarm.name,
id: swarm.id,
template: swarm.template,
tags: swarm.tags,
resource_type: ResourceTargetVariant::Swarm,
info: SwarmListItemInfo {
server_ids: swarm.config.server_ids,
state,
err,
},
}
}
async fn busy(_id: &String) -> anyhow::Result<bool> {
Ok(false)
}
// CREATE
fn create_operation() -> Operation {
Operation::CreateSwarm
}
fn user_can_create(user: &User) -> bool {
user.admin || !core_config().disable_non_admin_create
}
async fn validate_create_config(
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_create(
created: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
update_cache_for_swarm(created, true).await;
Ok(())
}
// UPDATE
fn update_operation() -> Operation {
Operation::UpdateSwarm
}
async fn validate_update_config(
_id: &str,
config: &mut Self::PartialConfig,
user: &User,
) -> anyhow::Result<()> {
validate_config(config, user).await
}
async fn post_update(
updated: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
update_cache_for_swarm(updated, true).await;
Ok(())
}
// RENAME
fn rename_operation() -> Operation {
Operation::RenameSwarm
}
// DELETE
fn delete_operation() -> Operation {
Operation::DeleteSwarm
}
async fn pre_delete(
_swarm: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
Ok(())
}
async fn post_delete(
swarm: &Self,
_update: &mut Update,
) -> anyhow::Result<()> {
swarm_status_cache().remove(&swarm.id).await;
Ok(())
}
}
#[instrument("ValidateSwarmConfig", skip_all)]
async fn validate_config(
config: &mut PartialSwarmConfig,
user: &User,
) -> anyhow::Result<()> {
if let Some(server_ids) = &mut config.server_ids {
let mut res = Vec::with_capacity(server_ids.capacity());
for server_id in server_ids.iter_mut() {
let server = get_check_permissions::<Server>(
server_id,
user,
PermissionLevel::Read.attach(),
)
.await
.with_context(|| {
format!("Cannot attach Server {server_id} to this Swarm")
})?;
res.push(server.id);
}
*server_ids = res;
}
Ok(())
}
// pub fn spawn_swarm_state_refresh_loop() {
// tokio::spawn(async move {
// loop {
// refresh_swarm_state_cache().await;
// tokio::time::sleep(Duration::from_secs(60)).await;
// }
// });
// }
// pub async fn refresh_swarm_state_cache() {
// let _ = async {
// let swarms = find_collect(&db_client().swarms, None, None)
// .await
// .context("failed to get swarms from db")?;
// let cache = swarm_state_cache();
// for swarm in swarms {
// let state = get_swarm_state_from_db(&swarm.id).await;
// cache.insert(swarm.id, state).await;
// }
// anyhow::Ok(())
// }
// .await
// .inspect_err(|e| {
// warn!("failed to refresh swarm state cache | {e:#}")
// });
// }
+75 -7
View File
@@ -4,9 +4,19 @@ use anyhow::Context;
use arc_swap::ArcSwap;
use cache::CloneCache;
use komodo_client::entities::{
action::ActionState, build::BuildState,
deployment::DeploymentState, procedure::ProcedureState,
repo::RepoState, stack::StackState,
action::ActionState,
build::BuildState,
deployment::DeploymentState,
docker::{
DockerLists, SwarmLists, container::ContainerListItem,
swarm::SwarmInspectInfo,
},
procedure::ProcedureState,
repo::RepoState,
server::{PeripheryInformation, ServerHealth, ServerState},
stack::{StackService, StackState},
stats::{SystemInformation, SystemStats},
swarm::SwarmState,
};
use crate::{
@@ -16,10 +26,6 @@ use crate::{
helpers::{
action_state::ActionStates, all_resources::AllResourcesById,
},
monitor::{
CachedDeploymentStatus, CachedRepoStatus, CachedServerStatus,
CachedStackStatus, History,
},
};
static DB_CLIENT: OnceLock<database::Client> = OnceLock::new();
@@ -64,6 +70,43 @@ pub fn action_states() -> &'static ActionStates {
ACTION_STATES.get_or_init(ActionStates::default)
}
#[derive(Default, Debug)]
pub struct History<Curr: Default, Prev> {
pub curr: Curr,
pub prev: Option<Prev>,
}
#[derive(Default, Clone, Debug)]
pub struct CachedSwarmStatus {
pub state: SwarmState,
pub inspect: Option<SwarmInspectInfo>,
pub lists: Option<SwarmLists>,
/// Store the error in communicating with Swarm
pub err: Option<String>,
}
pub type SwarmStatusCache =
CloneCache<String, Arc<CachedSwarmStatus>>;
pub fn swarm_status_cache() -> &'static SwarmStatusCache {
static SWARM_STATUS_CACHE: OnceLock<SwarmStatusCache> =
OnceLock::new();
SWARM_STATUS_CACHE.get_or_init(Default::default)
}
#[derive(Default, Clone, Debug)]
pub struct CachedServerStatus {
pub id: String,
pub state: ServerState,
pub health: Option<ServerHealth>,
pub periphery_info: Option<PeripheryInformation>,
pub system_info: Option<SystemInformation>,
pub system_stats: Option<SystemStats>,
pub docker: Option<DockerLists>,
/// Store the error in reaching periphery
pub err: Option<serror::Serror>,
}
pub type ServerStatusCache =
CloneCache<String, Arc<CachedServerStatus>>;
@@ -73,6 +116,16 @@ pub fn server_status_cache() -> &'static ServerStatusCache {
SERVER_STATUS_CACHE.get_or_init(Default::default)
}
#[derive(Default, Clone, Debug)]
pub struct CachedStackStatus {
/// The stack id
pub id: String,
/// The stack state
pub state: StackState,
/// The services connected to the stack
pub services: Vec<StackService>,
}
pub type StackStatusCache =
CloneCache<String, Arc<History<CachedStackStatus, StackState>>>;
@@ -82,6 +135,15 @@ pub fn stack_status_cache() -> &'static StackStatusCache {
STACK_STATUS_CACHE.get_or_init(Default::default)
}
#[derive(Default, Clone, Debug)]
pub struct CachedDeploymentStatus {
/// The deployment id
pub id: String,
pub state: DeploymentState,
pub container: Option<ContainerListItem>,
pub update_available: bool,
}
/// Cache of ids to status
pub type DeploymentStatusCache = CloneCache<
String,
@@ -103,6 +165,12 @@ pub fn build_state_cache() -> &'static BuildStateCache {
BUILD_STATE_CACHE.get_or_init(Default::default)
}
#[derive(Default, Clone, Debug)]
pub struct CachedRepoStatus {
pub latest_hash: Option<String>,
pub latest_message: Option<String>,
}
pub type RepoStatusCache = CloneCache<String, Arc<CachedRepoStatus>>;
pub fn repo_status_cache() -> &'static RepoStatusCache {
+12
View File
@@ -14,6 +14,7 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
swarm::Swarm,
sync::ResourceSync,
tag::Tag,
update::Log,
@@ -34,6 +35,17 @@ use super::{
include_resource_by_tags,
};
impl ResourceSyncTrait for Swarm {
fn get_diff(
original: Self::Config,
update: Self::PartialConfig,
) -> anyhow::Result<Self::ConfigDiff> {
Ok(original.partial_diff(update))
}
}
impl ExecuteResourceSync for Swarm {}
impl ResourceSyncTrait for Server {
fn get_diff(
original: Self::Config,
+37
View File
@@ -15,6 +15,7 @@ use komodo_client::{
resource::Resource,
server::Server,
stack::Stack,
swarm::Swarm,
sync::ResourceSync,
tag::Tag,
toml::ResourceToml,
@@ -174,6 +175,42 @@ impl ToToml for ResourceSync {
}
}
impl ToToml for Swarm {
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
let all = all_resources_cache().load();
let mut res =
Vec::with_capacity(resource.config.server_ids.capacity());
for server_id in &resource.config.server_ids {
res.push(
all
.servers
.get(server_id)
.map(|s| s.name.clone())
.unwrap_or(String::new()),
);
}
}
fn edit_config_object(
_resource: &ResourceToml<Self::PartialConfig>,
config: IndexMap<String, serde_json::Value>,
) -> anyhow::Result<IndexMap<String, serde_json::Value>> {
config
.into_iter()
.map(|(key, value)| {
#[allow(clippy::single_match)]
match key.as_str() {
"server_ids" => {
return Ok((String::from("servers"), value));
}
_ => {}
}
Ok((key, value))
})
.collect()
}
}
impl ToToml for Stack {
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
let all = all_resources_cache().load();
+112 -86
View File
@@ -323,23 +323,9 @@ pub async fn get_updates_for_execution(
// replace the ids with names
match &mut p.resource_target {
ResourceTarget::System(_) => {}
ResourceTarget::Build(id) => {
ResourceTarget::Swarm(id) => {
*id = all_resources
.builds
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Builder(id) => {
*id = all_resources
.builders
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Deployment(id) => {
*id = all_resources
.deployments
.swarms
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
@@ -351,16 +337,30 @@ pub async fn get_updates_for_execution(
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
ResourceTarget::Stack(id) => {
*id = all_resources
.repos
.stacks
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
ResourceTarget::Deployment(id) => {
*id = all_resources
.alerters
.deployments
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Build(id) => {
*id = all_resources
.builds
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
*id = all_resources
.repos
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
@@ -386,9 +386,16 @@ pub async fn get_updates_for_execution(
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Stack(id) => {
ResourceTarget::Builder(id) => {
*id = all_resources
.stacks
.builders
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
*id = all_resources
.alerters
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
@@ -796,37 +803,13 @@ async fn expand_user_group_permissions(
}
let matcher = Matcher::new(id)?;
match variant {
ResourceTargetVariant::Build => {
ResourceTargetVariant::Swarm => {
let permissions = all_resources
.builds
.swarms
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Build(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Builder => {
let permissions = all_resources
.builders
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Builder(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Deployment => {
let permissions = all_resources
.deployments
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Deployment(resource.name.clone()),
target: ResourceTarget::Swarm(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
@@ -844,6 +827,42 @@ async fn expand_user_group_permissions(
});
expanded.extend(permissions);
}
ResourceTargetVariant::Stack => {
let permissions = all_resources
.stacks
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Stack(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Deployment => {
let permissions = all_resources
.deployments
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Deployment(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Build => {
let permissions = all_resources
.builds
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Build(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Repo => {
let permissions = all_resources
.repos
@@ -856,18 +875,6 @@ async fn expand_user_group_permissions(
});
expanded.extend(permissions);
}
ResourceTargetVariant::Alerter => {
let permissions = all_resources
.alerters
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Alerter(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Procedure => {
let permissions = all_resources
.procedures
@@ -906,13 +913,25 @@ async fn expand_user_group_permissions(
});
expanded.extend(permissions);
}
ResourceTargetVariant::Stack => {
ResourceTargetVariant::Builder => {
let permissions = all_resources
.stacks
.builders
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Stack(resource.name.clone()),
target: ResourceTarget::Builder(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
expanded.extend(permissions);
}
ResourceTargetVariant::Alerter => {
let permissions = all_resources
.alerters
.values()
.filter(|resource| matcher.is_match(&resource.name))
.map(|resource| PermissionToml {
target: ResourceTarget::Alerter(resource.name.clone()),
level: permission.level,
specific: permission.specific.clone(),
});
@@ -1040,23 +1059,9 @@ pub async fn convert_user_groups(
.filter(|permission| permission.level > PermissionLevel::None)
.map(|mut permission| {
match &mut permission.resource_target {
ResourceTarget::Build(id) => {
ResourceTarget::Swarm(id) => {
*id = all
.builds
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Builder(id) => {
*id = all
.builders
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Deployment(id) => {
*id = all
.deployments
.swarms
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
@@ -1068,16 +1073,30 @@ pub async fn convert_user_groups(
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
ResourceTarget::Stack(id) => {
*id = all
.repos
.stacks
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
ResourceTarget::Deployment(id) => {
*id = all
.alerters
.deployments
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Build(id) => {
*id = all
.builds
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
*id = all
.repos
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
@@ -1103,9 +1122,16 @@ pub async fn convert_user_groups(
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Stack(id) => {
ResourceTarget::Builder(id) => {
*id = all
.stacks
.builders
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
*id = all
.alerters
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
+11 -13
View File
@@ -9,7 +9,7 @@ use formatting::format_serror;
use komodo_client::entities::{
FileContents, RepoExecutionArgs,
repo::Repo,
stack::{Stack, StackRemoteFileContents},
stack::{AdditionalEnvFile, Stack, StackRemoteFileContents},
to_path_compatible_name,
update::Log,
};
@@ -60,24 +60,22 @@ pub async fn maybe_login_registry(
pub fn env_file_args(
env_file_path: Option<&str>,
additional_env_files: &[String],
additional_env_files: &[AdditionalEnvFile],
) -> anyhow::Result<String> {
let mut res = String::new();
for file in additional_env_files.iter().filter(|&path| {
let Some(komodo_path) = env_file_path else {
return true;
};
// Filter komodo env out of additional env file if its also in there.
// It will be always be added last / have highest priority.
path != komodo_path
}) {
write!(res, " --env-file {file}").with_context(|| {
format!("Failed to write --env-file arg for {file}")
// Add additional env files (except komodo's own, which comes last)
for file in additional_env_files
.iter()
.filter(|f| env_file_path != Some(f.path.as_str()))
{
let path = &file.path;
write!(res, " --env-file {path}").with_context(|| {
format!("Failed to write --env-file arg for {path}")
})?;
}
// Add this last, so it is applied on top
// Add komodo's env file last for highest priority
if let Some(file) = env_file_path {
write!(res, " --env-file {file}").with_context(|| {
format!("Failed to write --env-file arg for {file}")
+30 -73
View File
@@ -8,23 +8,26 @@ use command::{
use formatting::format_serror;
use git::write_commit_file;
use interpolate::Interpolator;
use komodo_client::entities::{
FileContents, RepoExecutionResponse, all_logs_success,
stack::{
ComposeFile, ComposeProject, ComposeService,
ComposeServiceDeploy, StackRemoteFileContents, StackServiceNames,
use komodo_client::{
entities::{
FileContents, RepoExecutionResponse, all_logs_success,
stack::{
ComposeFile, ComposeService, ComposeServiceDeploy,
StackRemoteFileContents, StackServiceNames,
},
to_path_compatible_name,
update::Log,
},
to_path_compatible_name,
update::Log,
parsers::parse_multiline_command,
};
use periphery_client::api::compose::*;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use shell_escape::unix::escape;
use tracing::Instrument;
use crate::{
config::periphery_config,
docker::compose::docker_compose,
helpers::{format_extra_args, format_log_grep},
};
@@ -33,67 +36,6 @@ mod write;
use helpers::*;
fn docker_compose() -> &'static str {
if periphery_config().legacy_compose_cli {
"docker-compose"
} else {
"docker compose"
}
}
pub async fn list_compose_projects()
-> anyhow::Result<Vec<ComposeProject>> {
let docker_compose = docker_compose();
let res = run_komodo_standard_command(
"List Projects",
None,
format!("{docker_compose} ls --all --format json"),
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(format!(
"failed to list compose projects using {docker_compose} ls"
)));
}
let res =
serde_json::from_str::<Vec<DockerComposeLsItem>>(&res.stdout)
.with_context(|| res.stdout.clone())
.with_context(|| {
format!(
"failed to parse '{docker_compose} ls' response to json"
)
})?
.into_iter()
.filter(|item| !item.name.is_empty())
.map(|item| ComposeProject {
name: item.name,
status: item.status,
compose_files: item
.config_files
.split(',')
.map(str::to_string)
.collect(),
})
.collect();
Ok(res)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DockerComposeLsItem {
#[serde(default, alias = "Name")]
pub name: String,
#[serde(alias = "Status")]
pub status: Option<String>,
/// Comma seperated list of paths
#[serde(default, alias = "ConfigFiles")]
pub config_files: String,
}
//
impl Resolve<super::Args> for GetComposeLog {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let GetComposeLog {
@@ -113,7 +55,7 @@ impl Resolve<super::Args> for GetComposeLog {
services.join(" ")
);
Ok(
run_komodo_standard_command("get stack log", None, command)
run_komodo_standard_command("Get Stack Log", None, command)
.await,
)
}
@@ -141,7 +83,7 @@ impl Resolve<super::Args> for GetComposeLogSearch {
services.join(" ")
);
Ok(
run_komodo_shell_command("Get stack log grep", None, command)
run_komodo_shell_command("Search Stack Log", None, command)
.await,
)
}
@@ -361,7 +303,7 @@ impl Resolve<super::Args> for ComposePull {
)?;
let file_paths = stack
.all_file_paths()
.all_tracked_file_paths()
.into_iter()
.map(|path| {
(
@@ -658,10 +600,25 @@ impl Resolve<super::Args> for ComposeUp {
// Run compose up
let extra_args = format_extra_args(&stack.config.extra_args);
let command = format!(
let mut command = format!(
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} up -d{extra_args}{service_args}",
);
// Apply compose cmd wrapper if configured
let compose_cmd_wrapper =
parse_multiline_command(&stack.config.compose_cmd_wrapper);
if !compose_cmd_wrapper.is_empty() {
if !compose_cmd_wrapper.contains("[[COMPOSE_COMMAND]]") {
res.logs.push(Log::error(
"Compose Command Wrapper",
"compose_cmd_wrapper is configured but does not contain [[COMPOSE_COMMAND]] placeholder. The placeholder is required to inject the compose command.".to_string(),
));
return Ok(res);
}
command =
compose_cmd_wrapper.replace("[[COMPOSE_COMMAND]]", &command);
}
let span = info_span!("RunComposeUp");
let Some(log) = run_komodo_command_with_sanitization(
"Compose Up",
+15 -82
View File
@@ -1,28 +1,20 @@
use command::run_komodo_standard_command;
use derive_variants::EnumVariants;
use encoding::{EncodedJsonMessage, EncodedResponse};
use futures_util::FutureExt;
use komodo_client::entities::{
config::{DockerRegistry, GitProvider},
server::PeripheryInformation,
stats::SystemProcess,
update::Log,
};
use periphery_client::api::{
build::*, compose::*, container::*, docker::*, git::*, keys::*,
stats::*, terminal::*, *,
poll::*, stats::*, swarm::*, terminal::*, *,
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
api::compose::list_compose_projects,
config::periphery_config,
state::{
docker_client, host_public_ip, periphery_keys, stats_client,
},
};
use crate::{config::periphery_config, state::stats_client};
pub mod terminal;
@@ -33,6 +25,8 @@ mod deploy;
mod docker;
mod git;
mod keys;
mod poll;
mod swarm;
#[derive(Debug)]
pub struct Args {
@@ -142,6 +136,17 @@ pub enum PeripheryRequest {
// All in one (Write)
PruneSystem(PruneSystem),
// Swarm
PollSwarmStatus(PollSwarmStatus),
InspectSwarmNode(InspectSwarmNode),
InspectSwarmConfig(InspectSwarmConfig),
InspectSwarmSecret(InspectSwarmSecret),
InspectSwarmStack(InspectSwarmStack),
InspectSwarmTask(InspectSwarmTask),
InspectSwarmService(InspectSwarmService),
GetSwarmServiceLog(GetSwarmServiceLog),
GetSwarmServiceLogSearch(GetSwarmServiceLogSearch),
// Terminal
ListTerminals(ListTerminals),
CreateServerTerminal(CreateServerTerminal),
@@ -184,78 +189,6 @@ impl Resolve<Args> for GetVersion {
//
impl Resolve<Args> for PollStatus {
async fn resolve(
self,
_: &Args,
) -> anyhow::Result<PollStatusResponse> {
// Docker lists
let docker_lists = async {
let client = docker_client().load();
let Some(client) = client.iter().next() else {
return Default::default();
};
let containers =
client.list_containers().await.unwrap_or_default();
// Todo: handle errors better
(
tokio::join!(
client
.list_networks(&containers)
.map(Result::unwrap_or_default),
client
.list_images(&containers)
.map(Result::unwrap_or_default),
client
.list_volumes(&containers)
.map(Result::unwrap_or_default)
),
containers,
)
};
let (
((networks, images, volumes), containers),
projects,
stats_client,
) = tokio::join!(
docker_lists,
list_compose_projects().map(Result::unwrap_or_default),
stats_client().read(),
);
let system_stats = if self.include_stats {
Some(stats_client.stats.clone())
} else {
None
};
let config = periphery_config();
Ok(PollStatusResponse {
periphery_info: PeripheryInformation {
version: env!("CARGO_PKG_VERSION").to_string(),
public_key: periphery_keys().load().public.to_string(),
terminals_disabled: config.disable_terminals,
container_terminals_disabled: config
.disable_container_terminals,
stats_polling_rate: config.stats_polling_rate,
docker_connected: docker_client().load().is_some(),
public_ip: host_public_ip().await.cloned(),
},
system_info: stats_client.info.clone(),
system_stats,
containers,
networks,
images,
volumes,
projects,
})
}
}
//
impl Resolve<Args> for GetSystemProcesses {
async fn resolve(
self,
+83
View File
@@ -0,0 +1,83 @@
use futures_util::FutureExt;
use komodo_client::entities::{
docker::DockerLists, server::PeripheryInformation,
};
use periphery_client::api::poll::{PollStatus, PollStatusResponse};
use resolver_api::Resolve;
use crate::{
config::periphery_config,
docker::{DockerClient, compose::list_compose_projects},
state::{
docker_client, host_public_ip, periphery_keys, stats_client,
},
};
impl Resolve<super::Args> for PollStatus {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<PollStatusResponse> {
let stats_client = stats_client().read().await;
let system_stats = if self.include_stats {
Some(stats_client.stats.clone())
} else {
None
};
let docker = if self.include_docker {
let client = docker_client().load();
if let Some(client) = client.iter().next() {
Some(docker_lists(client).await)
} else {
None
}
} else {
None
};
Ok(PollStatusResponse {
periphery_info: periphery_information().await,
system_info: stats_client.info.clone(),
system_stats,
docker,
})
}
}
async fn periphery_information() -> PeripheryInformation {
let config = periphery_config();
PeripheryInformation {
version: env!("CARGO_PKG_VERSION").to_string(),
public_key: periphery_keys().load().public.to_string(),
terminals_disabled: config.disable_terminals,
container_terminals_disabled: config.disable_container_terminals,
stats_polling_rate: config.stats_polling_rate,
docker_connected: docker_client().load().is_some(),
public_ip: host_public_ip().await.cloned(),
}
}
async fn docker_lists(client: &DockerClient) -> DockerLists {
let containers = client.list_containers().await.unwrap_or_default();
let (networks, images, volumes, projects) = tokio::join!(
client
.list_networks(&containers)
.map(Result::unwrap_or_default),
client
.list_images(&containers)
.map(Result::unwrap_or_default),
client
.list_volumes(&containers)
.map(Result::unwrap_or_default),
list_compose_projects().map(Result::unwrap_or_default),
);
DockerLists {
containers,
networks,
images,
volumes,
projects,
}
}
+219
View File
@@ -0,0 +1,219 @@
use anyhow::Context as _;
use command::{
run_komodo_shell_command, run_komodo_standard_command,
};
use komodo_client::entities::{
docker::{
SwarmLists, config::SwarmConfig, node::SwarmNode,
secret::SwarmSecret, service::SwarmService,
stack::SwarmStackLists, task::SwarmTask,
},
update::Log,
};
use periphery_client::api::swarm::*;
use resolver_api::Resolve;
use crate::{
docker::{
config::{inspect_swarm_config, list_swarm_configs},
stack::{inspect_swarm_stack, list_swarm_stacks},
},
helpers::format_log_grep,
state::docker_client,
};
impl Resolve<super::Args> for PollSwarmStatus {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<PollSwarmStatusResponse> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
let (inspect, nodes, services, tasks, secrets, configs, stacks) = tokio::join!(
client.inspect_swarm(),
client.list_swarm_nodes(),
client.list_swarm_services(),
client.list_swarm_tasks(),
client.list_swarm_secrets(),
list_swarm_configs(),
list_swarm_stacks(),
);
Ok(PollSwarmStatusResponse {
inspect: inspect.ok(),
lists: SwarmLists {
nodes: nodes.unwrap_or_default(),
services: services.unwrap_or_default(),
tasks: tasks.unwrap_or_default(),
secrets: secrets.unwrap_or_default(),
configs: configs.unwrap_or_default(),
stacks: stacks.unwrap_or_default(),
},
})
}
}
// ======
// Node
// ======
impl Resolve<super::Args> for InspectSwarmNode {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmNode> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_node(&self.node).await
}
}
// =========
// Service
// =========
impl Resolve<super::Args> for InspectSwarmService {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmService> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_service(&self.service).await
}
}
impl Resolve<super::Args> for GetSwarmServiceLog {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let GetSwarmServiceLog {
service,
tail,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let no_task_ids =
no_task_ids.then_some(" --no-task-ids").unwrap_or_default();
let no_resolve =
no_resolve.then_some(" --no-resolve").unwrap_or_default();
let details = details.then_some(" --details").unwrap_or_default();
let command = format!(
"docker service logs --tail {tail}{timestamps}{no_task_ids}{no_resolve}{details} {service}",
);
Ok(
run_komodo_standard_command(
"Get Swarm Service Log",
None,
command,
)
.await,
)
}
}
impl Resolve<super::Args> for GetSwarmServiceLogSearch {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let GetSwarmServiceLogSearch {
service,
terms,
combinator,
invert,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let no_task_ids =
no_task_ids.then_some(" --no-task-ids").unwrap_or_default();
let no_resolve =
no_resolve.then_some(" --no-resolve").unwrap_or_default();
let details = details.then_some(" --details").unwrap_or_default();
let grep = format_log_grep(&terms, combinator, invert);
let command = format!(
"docker service logs --tail 5000{timestamps}{no_task_ids}{no_resolve}{details} {service} 2>&1 | {grep}",
);
Ok(
run_komodo_shell_command(
"Search Swarm Service Log",
None,
command,
)
.await,
)
}
}
// ======
// Task
// ======
impl Resolve<super::Args> for InspectSwarmTask {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmTask> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_task(&self.task).await
}
}
// ========
// Secret
// ========
impl Resolve<super::Args> for InspectSwarmSecret {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmSecret> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_secret(&self.secret).await
}
}
// ========
// Config
// ========
impl Resolve<super::Args> for InspectSwarmConfig {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<Vec<SwarmConfig>> {
inspect_swarm_config(&self.config).await
}
}
// =======
// Stack
// =======
impl Resolve<super::Args> for InspectSwarmStack {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmStackLists> {
inspect_swarm_stack(self.stack).await
}
}
+2 -1
View File
@@ -171,7 +171,8 @@ async fn handle_onboarding(
public_key_validator: core_public_keys(),
socket: &mut socket,
})
.await?;
.await
.context("Onboarding failed")?;
// Post onboarding login 1: Send public key
socket
+65
View File
@@ -0,0 +1,65 @@
use anyhow::{Context, anyhow};
use command::run_komodo_standard_command;
use komodo_client::entities::stack::ComposeProject;
use serde::{Deserialize, Serialize};
use crate::config::periphery_config;
pub fn docker_compose() -> &'static str {
if periphery_config().legacy_compose_cli {
"docker-compose"
} else {
"docker compose"
}
}
pub async fn list_compose_projects()
-> anyhow::Result<Vec<ComposeProject>> {
let docker_compose = docker_compose();
let res = run_komodo_standard_command(
"List Projects",
None,
format!("{docker_compose} ls --all --format json"),
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(format!(
"Failed to list compose projects using {docker_compose} ls"
)));
}
let res =
serde_json::from_str::<Vec<DockerComposeLsItem>>(&res.stdout)
.with_context(|| res.stdout.clone())
.with_context(|| {
format!(
"Failed to parse '{docker_compose} ls' response from json"
)
})?
.into_iter()
.filter(|item| !item.name.is_empty())
.map(|item| ComposeProject {
name: item.name,
status: item.status,
compose_files: item
.config_files
.split(',')
.map(str::to_string)
.collect(),
})
.collect();
Ok(res)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DockerComposeLsItem {
#[serde(default, alias = "Name")]
pub name: String,
#[serde(alias = "Status")]
pub status: Option<String>,
/// Comma seperated list of paths
#[serde(default, alias = "ConfigFiles")]
pub config_files: String,
}
+49
View File
@@ -0,0 +1,49 @@
use anyhow::{Context, anyhow};
use command::run_komodo_standard_command;
use komodo_client::entities::docker::config::{
SwarmConfig, SwarmConfigListItem,
};
pub async fn list_swarm_configs()
-> anyhow::Result<Vec<SwarmConfigListItem>> {
let res = run_komodo_standard_command(
"List Swarm Configs",
None,
"docker config ls --format json",
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(format!(
"Failed to list swarm configs using 'docker config ls'"
)));
}
// The output is in JSONL, need to convert to standard JSON vec.
serde_json::from_str(&format!(
"[{}]",
res.stdout.trim().replace('\n', ",")
))
.context("Failed to parse 'docker config ls' response from json")
}
pub async fn inspect_swarm_config(
config: &str,
) -> anyhow::Result<Vec<SwarmConfig>> {
let res = run_komodo_standard_command(
"Inspect Swarm Config",
None,
format!(r#"docker config inspect "{config}""#),
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(format!(
"Failed to inspect swarm config using 'docker config inspect {config}'"
)));
}
serde_json::from_str(&res.stdout).context(
"Failed to parse 'docker config inspect' response from json",
)
}
@@ -4,14 +4,14 @@ use anyhow::Context;
use bollard::query_parameters::{
InspectContainerOptions, ListContainersOptions,
};
use komodo_client::entities::docker::{
ContainerConfig, GraphDriverData, HealthConfig, PortBinding,
container::*,
};
use komodo_client::entities::docker::{container::*, *};
use crate::state::container_stats;
use super::DockerClient;
use super::{
DockerClient, convert_health_config, convert_mount,
convert_mount_point_type, convert_resources_ulimits,
};
impl DockerClient {
pub async fn list_containers(
@@ -257,11 +257,7 @@ impl DockerClient {
.ulimits
.unwrap_or_default()
.into_iter()
.map(|ulimit| ResourcesUlimits {
name: ulimit.name,
soft: ulimit.soft,
hard: ulimit.hard,
})
.map(convert_resources_ulimits)
.collect(),
cpu_count: config.cpu_count,
cpu_percent: config.cpu_percent,
@@ -309,49 +305,7 @@ impl DockerClient {
.mounts
.unwrap_or_default()
.into_iter()
.map(|mount| ContainerMount {
target: mount.target,
source: mount.source,
typ: mount
.typ
.map(convert_mount_type)
.unwrap_or_default(),
read_only: mount.read_only,
consistency: mount.consistency,
bind_options: mount.bind_options.map(|options| {
MountBindOptions {
propagation: options
.propagation
.map(convert_mount_propogation)
.unwrap_or_default(),
non_recursive: options.non_recursive,
create_mountpoint: options.create_mountpoint,
read_only_non_recursive: options
.read_only_non_recursive,
read_only_force_recursive: options
.read_only_force_recursive,
}
}),
volume_options: mount.volume_options.map(|options| {
MountVolumeOptions {
no_copy: options.no_copy,
labels: options.labels.unwrap_or_default(),
driver_config: options.driver_config.map(|config| {
MountVolumeOptionsDriverConfig {
name: config.name,
options: config.options.unwrap_or_default(),
}
}),
subpath: options.subpath,
}
}),
tmpfs_options: mount.tmpfs_options.map(|options| {
MountTmpfsOptions {
size_bytes: options.size_bytes,
mode: options.mode,
}
}),
})
.map(convert_mount)
.collect(),
console_size: config
.console_size
@@ -435,14 +389,7 @@ impl DockerClient {
stdin_once: config.stdin_once,
env: config.env.unwrap_or_default(),
cmd: config.cmd.unwrap_or_default(),
healthcheck: config.healthcheck.map(|health| HealthConfig {
test: health.test.unwrap_or_default(),
interval: health.interval,
timeout: health.timeout,
retries: health.retries,
start_period: health.start_period,
start_interval: health.start_interval,
}),
healthcheck: config.healthcheck.map(convert_health_config),
args_escaped: config.args_escaped,
image: config.image,
volumes: config
@@ -657,74 +604,6 @@ fn convert_restart_policy(
}
}
fn convert_mount_type(
typ: bollard::secret::MountTypeEnum,
) -> MountTypeEnum {
match typ {
bollard::secret::MountTypeEnum::EMPTY => MountTypeEnum::Empty,
bollard::secret::MountTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountTypeEnum::VOLUME => MountTypeEnum::Volume,
bollard::secret::MountTypeEnum::IMAGE => MountTypeEnum::Image,
bollard::secret::MountTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
bollard::secret::MountTypeEnum::NPIPE => MountTypeEnum::Npipe,
bollard::secret::MountTypeEnum::CLUSTER => MountTypeEnum::Cluster,
}
}
fn convert_mount_point_type(
typ: bollard::secret::MountPointTypeEnum,
) -> MountTypeEnum {
match typ {
bollard::secret::MountPointTypeEnum::EMPTY => {
MountTypeEnum::Empty
}
bollard::secret::MountPointTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountPointTypeEnum::VOLUME => {
MountTypeEnum::Volume
}
bollard::secret::MountPointTypeEnum::IMAGE => {
MountTypeEnum::Image
}
bollard::secret::MountPointTypeEnum::TMPFS => {
MountTypeEnum::Tmpfs
}
bollard::secret::MountPointTypeEnum::NPIPE => {
MountTypeEnum::Npipe
}
bollard::secret::MountPointTypeEnum::CLUSTER => {
MountTypeEnum::Cluster
}
}
}
fn convert_mount_propogation(
propogation: bollard::secret::MountBindOptionsPropagationEnum,
) -> MountBindOptionsPropagationEnum {
match propogation {
bollard::secret::MountBindOptionsPropagationEnum::EMPTY => {
MountBindOptionsPropagationEnum::Empty
}
bollard::secret::MountBindOptionsPropagationEnum::PRIVATE => {
MountBindOptionsPropagationEnum::Private
}
bollard::secret::MountBindOptionsPropagationEnum::RPRIVATE => {
MountBindOptionsPropagationEnum::Rprivate
}
bollard::secret::MountBindOptionsPropagationEnum::SHARED => {
MountBindOptionsPropagationEnum::Shared
}
bollard::secret::MountBindOptionsPropagationEnum::RSHARED => {
MountBindOptionsPropagationEnum::Rshared
}
bollard::secret::MountBindOptionsPropagationEnum::SLAVE => {
MountBindOptionsPropagationEnum::Slave
}
bollard::secret::MountBindOptionsPropagationEnum::RSLAVE => {
MountBindOptionsPropagationEnum::Rslave
}
}
}
fn convert_cgroupns_mode(
mode: bollard::secret::HostConfigCgroupnsModeEnum,
) -> HostConfigCgroupnsModeEnum {
+433 -5
View File
@@ -1,14 +1,26 @@
use anyhow::{Context, anyhow};
use bollard::Docker;
use command::{run_komodo_standard_command, run_shell_command};
use komodo_client::entities::{TerminationSignal, update::Log};
use komodo_client::entities::{
TerminationSignal,
docker::{task::*, *},
update::Log,
};
pub mod compose;
pub mod config;
pub mod stack;
pub mod stats;
mod containers;
mod images;
mod networks;
mod volumes;
mod container;
mod image;
mod network;
mod node;
mod secret;
mod service;
mod swarm;
mod task;
mod volume;
pub struct DockerClient {
docker: Docker,
@@ -78,3 +90,419 @@ pub fn stop_container_command(
.unwrap_or_default();
format!("docker stop{signal}{time} {container_name}")
}
fn convert_object_version(
version: bollard::models::ObjectVersion,
) -> ObjectVersion {
ObjectVersion {
index: version.index,
}
}
fn convert_driver(driver: bollard::models::Driver) -> Driver {
Driver {
name: driver.name,
options: driver.options,
}
}
fn convert_mount(mount: bollard::models::Mount) -> Mount {
Mount {
target: mount.target,
source: mount.source,
typ: mount.typ.map(convert_mount_type).unwrap_or_default(),
read_only: mount.read_only,
consistency: mount.consistency,
bind_options: mount.bind_options.map(|options| {
MountBindOptions {
propagation: options
.propagation
.map(convert_mount_propogation)
.unwrap_or_default(),
non_recursive: options.non_recursive,
create_mountpoint: options.create_mountpoint,
read_only_non_recursive: options.read_only_non_recursive,
read_only_force_recursive: options.read_only_force_recursive,
}
}),
volume_options: mount.volume_options.map(|options| {
MountVolumeOptions {
no_copy: options.no_copy,
labels: options.labels.unwrap_or_default(),
driver_config: options.driver_config.map(|config| {
MountVolumeOptionsDriverConfig {
name: config.name,
options: config.options.unwrap_or_default(),
}
}),
subpath: options.subpath,
}
}),
tmpfs_options: mount.tmpfs_options.map(|options| {
MountTmpfsOptions {
size_bytes: options.size_bytes,
mode: options.mode,
}
}),
}
}
fn convert_mount_type(
typ: bollard::secret::MountTypeEnum,
) -> MountTypeEnum {
match typ {
bollard::secret::MountTypeEnum::EMPTY => MountTypeEnum::Empty,
bollard::secret::MountTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountTypeEnum::VOLUME => MountTypeEnum::Volume,
bollard::secret::MountTypeEnum::IMAGE => MountTypeEnum::Image,
bollard::secret::MountTypeEnum::TMPFS => MountTypeEnum::Tmpfs,
bollard::secret::MountTypeEnum::NPIPE => MountTypeEnum::Npipe,
bollard::secret::MountTypeEnum::CLUSTER => MountTypeEnum::Cluster,
}
}
fn convert_mount_propogation(
propogation: bollard::secret::MountBindOptionsPropagationEnum,
) -> MountBindOptionsPropagationEnum {
match propogation {
bollard::secret::MountBindOptionsPropagationEnum::EMPTY => {
MountBindOptionsPropagationEnum::Empty
}
bollard::secret::MountBindOptionsPropagationEnum::PRIVATE => {
MountBindOptionsPropagationEnum::Private
}
bollard::secret::MountBindOptionsPropagationEnum::RPRIVATE => {
MountBindOptionsPropagationEnum::Rprivate
}
bollard::secret::MountBindOptionsPropagationEnum::SHARED => {
MountBindOptionsPropagationEnum::Shared
}
bollard::secret::MountBindOptionsPropagationEnum::RSHARED => {
MountBindOptionsPropagationEnum::Rshared
}
bollard::secret::MountBindOptionsPropagationEnum::SLAVE => {
MountBindOptionsPropagationEnum::Slave
}
bollard::secret::MountBindOptionsPropagationEnum::RSLAVE => {
MountBindOptionsPropagationEnum::Rslave
}
}
}
fn convert_mount_point_type(
typ: bollard::secret::MountPointTypeEnum,
) -> MountTypeEnum {
match typ {
bollard::secret::MountPointTypeEnum::EMPTY => {
MountTypeEnum::Empty
}
bollard::secret::MountPointTypeEnum::BIND => MountTypeEnum::Bind,
bollard::secret::MountPointTypeEnum::VOLUME => {
MountTypeEnum::Volume
}
bollard::secret::MountPointTypeEnum::IMAGE => {
MountTypeEnum::Image
}
bollard::secret::MountPointTypeEnum::TMPFS => {
MountTypeEnum::Tmpfs
}
bollard::secret::MountPointTypeEnum::NPIPE => {
MountTypeEnum::Npipe
}
bollard::secret::MountPointTypeEnum::CLUSTER => {
MountTypeEnum::Cluster
}
}
}
fn convert_health_config(
config: bollard::models::HealthConfig,
) -> HealthConfig {
HealthConfig {
test: config.test.unwrap_or_default(),
interval: config.interval,
timeout: config.timeout,
retries: config.retries,
start_period: config.start_period,
start_interval: config.start_interval,
}
}
fn convert_resources_ulimits(
ulimit: bollard::models::ResourcesUlimits,
) -> ResourcesUlimits {
ResourcesUlimits {
name: ulimit.name,
soft: ulimit.soft,
hard: ulimit.hard,
}
}
fn convert_resource_object(
object: bollard::models::ResourceObject,
) -> ResourceObject {
ResourceObject {
nano_cpus: object.nano_cpus,
memory_bytes: object.memory_bytes,
generic_resources: object
.generic_resources
.map(convert_generic_resources),
}
}
fn convert_generic_resources(
resources: Vec<bollard::models::GenericResourcesInner>,
) -> Vec<GenericResourcesInner> {
resources
.into_iter()
.map(|resource| GenericResourcesInner {
named_resource_spec: resource.named_resource_spec.map(|spec| {
GenericResourcesInnerNamedResourceSpec {
kind: spec.kind,
value: spec.value,
}
}),
discrete_resource_spec: resource.discrete_resource_spec.map(
|spec| GenericResourcesInnerDiscreteResourceSpec {
kind: spec.kind,
value: spec.value,
},
),
})
.collect()
}
fn convert_platform(platform: bollard::models::Platform) -> Platform {
Platform {
architecture: platform.architecture,
os: platform.os,
}
}
fn convert_endpoint_spec_ports(
ports: Vec<bollard::models::EndpointPortConfig>,
) -> Vec<EndpointPortConfig> {
ports
.into_iter()
.map(|port| EndpointPortConfig {
name: port.name,
protocol: port.protocol.map(|protocol| match protocol {
bollard::secret::EndpointPortConfigProtocolEnum::EMPTY => EndpointPortConfigProtocolEnum::EMPTY,
bollard::secret::EndpointPortConfigProtocolEnum::TCP => EndpointPortConfigProtocolEnum::TCP,
bollard::secret::EndpointPortConfigProtocolEnum::UDP => EndpointPortConfigProtocolEnum::UDP,
bollard::secret::EndpointPortConfigProtocolEnum::SCTP => EndpointPortConfigProtocolEnum::SCTP,
}),
target_port: port.target_port,
published_port: port.published_port,
publish_mode: port.publish_mode.map(|protocol| match protocol {
bollard::secret::EndpointPortConfigPublishModeEnum::EMPTY => EndpointPortConfigPublishModeEnum::EMPTY,
bollard::secret::EndpointPortConfigPublishModeEnum::INGRESS => EndpointPortConfigPublishModeEnum::INGRESS,
bollard::secret::EndpointPortConfigPublishModeEnum::HOST => EndpointPortConfigPublishModeEnum::HOST,
}),
})
.collect()
}
fn convert_task_spec(spec: bollard::models::TaskSpec) -> TaskSpec {
TaskSpec {
plugin_spec: spec.plugin_spec.map(|spec| TaskSpecPluginSpec {
name: spec.name,
remote: spec.remote,
disabled: spec.disabled,
plugin_privilege: spec.plugin_privilege.map(|privileges| {
privileges
.into_iter()
.map(|privilege| PluginPrivilege {
name: privilege.name,
description: privilege.description,
value: privilege.value,
})
.collect()
}),
}),
container_spec: spec
.container_spec
.map(convert_task_spec_container_spec),
network_attachment_spec: spec.network_attachment_spec.map(
|spec| TaskSpecNetworkAttachmentSpec {
container_id: spec.container_id,
},
),
resources: spec.resources.map(|resources| TaskSpecResources {
limits: resources.limits.map(|limits| Limit {
nano_cpus: limits.nano_cpus,
memory_bytes: limits.memory_bytes,
pids: limits.pids,
}),
reservations: resources
.reservations
.map(convert_resource_object),
}),
restart_policy: spec.restart_policy.map(|policy| {
TaskSpecRestartPolicy {
condition: policy
.condition
.map(convert_task_spec_restart_policy_condition),
delay: policy.delay,
max_attempts: policy.max_attempts,
window: policy.window,
}
}),
placement: spec.placement.map(|placement| TaskSpecPlacement {
constraints: placement.constraints,
preferences: placement.preferences.map(|preferences| {
preferences
.into_iter()
.map(|preference| TaskSpecPlacementPreferences {
spread: preference.spread.map(|spread| {
TaskSpecPlacementSpread {
spread_descriptor: spread.spread_descriptor,
}
}),
})
.collect()
}),
max_replicas: placement.max_replicas,
platforms: placement.platforms.map(|platforms| {
platforms.into_iter().map(convert_platform).collect()
}),
}),
force_update: spec.force_update,
runtime: spec.runtime,
networks: spec.networks.map(|networks| {
networks
.into_iter()
.map(|network| NetworkAttachmentConfig {
target: network.target,
aliases: network.aliases,
driver_opts: network.driver_opts,
})
.collect()
}),
log_driver: spec.log_driver.map(|driver| TaskSpecLogDriver {
name: driver.name,
options: driver.options,
}),
}
}
fn convert_task_spec_container_spec(
spec: bollard::models::TaskSpecContainerSpec,
) -> TaskSpecContainerSpec {
TaskSpecContainerSpec {
image: spec.image,
labels: spec.labels,
command: spec.command,
args: spec.args,
hostname: spec.hostname,
env: spec.env,
dir: spec.dir,
user: spec.user,
groups: spec.groups,
privileges: spec.privileges.map(|privilege| {
TaskSpecContainerSpecPrivileges {
credential_spec: privilege.credential_spec.map(|spec| {
TaskSpecContainerSpecPrivilegesCredentialSpec {
config: spec.config,
file: spec.file,
registry: spec.registry,
}
}),
se_linux_context: privilege.se_linux_context.map(|context| {
TaskSpecContainerSpecPrivilegesSeLinuxContext {
disable: context.disable,
user: context.user,
role: context.role,
typ: context.typ,
level: context.level,
}
}),
seccomp: privilege.seccomp.map(|seccomp| {
TaskSpecContainerSpecPrivilegesSeccomp {
mode: seccomp.mode.map(|mode| match mode {
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::EMPTY => TaskSpecContainerSpecPrivilegesSeccompModeEnum::EMPTY,
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::DEFAULT => TaskSpecContainerSpecPrivilegesSeccompModeEnum::DEFAULT,
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::UNCONFINED => TaskSpecContainerSpecPrivilegesSeccompModeEnum::UNCONFINED,
bollard::secret::TaskSpecContainerSpecPrivilegesSeccompModeEnum::CUSTOM => TaskSpecContainerSpecPrivilegesSeccompModeEnum::CUSTOM,
}),
profile: seccomp.profile,
}
}),
app_armor: privilege.app_armor.map(|app_armor| {
TaskSpecContainerSpecPrivilegesAppArmor {
mode: app_armor.mode.map(|mode| match mode {
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::EMPTY => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::EMPTY,
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DEFAULT => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DEFAULT,
bollard::secret::TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DISABLED => TaskSpecContainerSpecPrivilegesAppArmorModeEnum::DISABLED,
}),
}
}),
no_new_privileges: privilege.no_new_privileges,
}
}),
tty: spec.tty,
open_stdin: spec.open_stdin,
read_only: spec.read_only,
mounts: spec.mounts.map(|mounts| mounts.into_iter().map(convert_mount).collect()),
stop_signal: spec.stop_signal,
stop_grace_period: spec.stop_grace_period,
health_check: spec.health_check.map(convert_health_config),
hosts: spec.hosts,
dns_config: spec.dns_config.map(|config| TaskSpecContainerSpecDnsConfig {
nameservers: config.nameservers,
search: config.search,
options: config.options,
}),
secrets: spec.secrets.map(|secrets| secrets.into_iter().map(|secret| TaskSpecContainerSpecSecrets {
file: secret.file.map(|file| TaskSpecContainerSpecFile {
name: file.name,
uid: file.uid,
gid: file.gid,
mode: file.mode,
}),
secret_id: secret.secret_id,
secret_name: secret.secret_name,
}).collect()),
oom_score_adj: spec.oom_score_adj,
configs: spec.configs.map(|configs| configs.into_iter().map(|config| TaskSpecContainerSpecConfigs {
file: config.file.map(|file| TaskSpecContainerSpecFile {
name: file.name,
uid: file.uid,
gid: file.gid,
mode: file.mode,
}),
config_id: config.config_id,
config_name: config.config_name,
}).collect()),
isolation: spec.isolation.map(|isolation| match isolation {
bollard::secret::TaskSpecContainerSpecIsolationEnum::DEFAULT => TaskSpecContainerSpecIsolationEnum::DEFAULT,
bollard::secret::TaskSpecContainerSpecIsolationEnum::PROCESS => TaskSpecContainerSpecIsolationEnum::PROCESS,
bollard::secret::TaskSpecContainerSpecIsolationEnum::HYPERV => TaskSpecContainerSpecIsolationEnum::HYPERV,
bollard::secret::TaskSpecContainerSpecIsolationEnum::EMPTY => TaskSpecContainerSpecIsolationEnum::EMPTY,
}),
init: spec.init,
sysctls: spec.sysctls,
capability_add: spec.capability_add,
capability_drop: spec.capability_drop,
ulimits: spec.ulimits.map(|ulimits| ulimits.into_iter().map(convert_resources_ulimits).collect()),
}
}
fn convert_task_spec_restart_policy_condition(
condition: bollard::secret::TaskSpecRestartPolicyConditionEnum,
) -> TaskSpecRestartPolicyConditionEnum {
match condition {
bollard::secret::TaskSpecRestartPolicyConditionEnum::EMPTY => TaskSpecRestartPolicyConditionEnum::EMPTY,
bollard::secret::TaskSpecRestartPolicyConditionEnum::NONE => TaskSpecRestartPolicyConditionEnum::NONE,
bollard::secret::TaskSpecRestartPolicyConditionEnum::ON_FAILURE => TaskSpecRestartPolicyConditionEnum::ON_FAILURE,
bollard::secret::TaskSpecRestartPolicyConditionEnum::ANY => TaskSpecRestartPolicyConditionEnum::ANY,
}
}
fn convert_tls_info(tls_info: bollard::models::TlsInfo) -> TlsInfo {
TlsInfo {
trust_root: tls_info.trust_root,
cert_issuer_subject: tls_info.cert_issuer_subject,
cert_issuer_public_key: tls_info.cert_issuer_public_key,
}
}
+180
View File
@@ -0,0 +1,180 @@
use anyhow::Context;
use bollard::query_parameters::ListNodesOptions;
use komodo_client::entities::docker::node::*;
use super::{
DockerClient, convert_platform, convert_resource_object,
};
impl DockerClient {
/// Lists swarm nodes
pub async fn list_swarm_nodes(
&self,
) -> anyhow::Result<Vec<SwarmNodeListItem>> {
let nodes = self
.docker
.list_nodes(Option::<ListNodesOptions>::None)
.await
.context("Failed to query for swarm node list")?
.into_iter()
.map(convert_node_list_item)
.collect();
Ok(nodes)
}
/// Inspect a swarm node
pub async fn inspect_swarm_node(
&self,
node_name: &str,
) -> anyhow::Result<SwarmNode> {
self
.docker
.inspect_node(node_name)
.await
.map(convert_node)
.with_context(|| {
format!(
"Failed to query for swarm node with name {node_name}"
)
})
}
}
fn convert_node_list_item(
node: bollard::models::Node,
) -> SwarmNodeListItem {
let (name, role, availability) = node
.spec
.map(|spec| {
(
spec.name,
spec.role.map(convert_role),
spec.availability.map(convert_availability),
)
})
.unwrap_or_default();
SwarmNodeListItem {
id: node.id,
name,
role,
availability,
hostname: node
.description
.and_then(|description| description.hostname),
state: node
.status
.and_then(|status| status.state.map(convert_state)),
created_at: node.created_at,
updated_at: node.updated_at,
}
}
fn convert_node(node: bollard::models::Node) -> SwarmNode {
SwarmNode {
id: node.id,
version: node.version.map(super::convert_object_version),
created_at: node.created_at,
updated_at: node.updated_at,
spec: node.spec.map(convert_node_spec),
description: node.description.map(|description| {
NodeDescription {
hostname: description.hostname,
platform: description.platform.map(convert_platform),
resources: description.resources.map(convert_resource_object),
engine: description.engine.map(|engine| EngineDescription {
engine_version: engine.engine_version,
labels: engine.labels,
plugins: engine.plugins.map(|plugins| {
plugins
.into_iter()
.map(|plugin| EngineDescriptionPlugins {
typ: plugin.typ,
name: plugin.name,
})
.collect()
}),
}),
tls_info: description.tls_info.map(super::convert_tls_info),
}
}),
status: node.status.map(|status| NodeStatus {
state: status.state.map(convert_state),
message: status.message,
addr: status.addr,
}),
manager_status: node.manager_status.map(|manager_status| {
ManagerStatus {
leader: manager_status.leader,
reachability: manager_status.reachability.map(
|reachability| match reachability {
bollard::secret::Reachability::UNKNOWN => {
NodeReachability::UNKNOWN
}
bollard::secret::Reachability::UNREACHABLE => {
NodeReachability::UNREACHABLE
}
bollard::secret::Reachability::REACHABLE => {
NodeReachability::REACHABLE
}
},
),
addr: manager_status.addr,
}
}),
}
}
fn convert_node_spec(spec: bollard::models::NodeSpec) -> NodeSpec {
NodeSpec {
name: spec.name,
labels: spec.labels,
role: spec.role.map(convert_role),
availability: spec.availability.map(convert_availability),
}
}
fn convert_role(
role: bollard::secret::NodeSpecRoleEnum,
) -> NodeSpecRoleEnum {
match role {
bollard::secret::NodeSpecRoleEnum::EMPTY => {
NodeSpecRoleEnum::EMPTY
}
bollard::secret::NodeSpecRoleEnum::WORKER => {
NodeSpecRoleEnum::WORKER
}
bollard::secret::NodeSpecRoleEnum::MANAGER => {
NodeSpecRoleEnum::MANAGER
}
}
}
fn convert_availability(
availability: bollard::secret::NodeSpecAvailabilityEnum,
) -> NodeSpecAvailabilityEnum {
match availability {
bollard::secret::NodeSpecAvailabilityEnum::EMPTY => {
NodeSpecAvailabilityEnum::EMPTY
}
bollard::secret::NodeSpecAvailabilityEnum::ACTIVE => {
NodeSpecAvailabilityEnum::ACTIVE
}
bollard::secret::NodeSpecAvailabilityEnum::PAUSE => {
NodeSpecAvailabilityEnum::PAUSE
}
bollard::secret::NodeSpecAvailabilityEnum::DRAIN => {
NodeSpecAvailabilityEnum::DRAIN
}
}
}
fn convert_state(state: bollard::secret::NodeState) -> NodeState {
match state {
bollard::secret::NodeState::UNKNOWN => NodeState::UNKNOWN,
bollard::secret::NodeState::DOWN => NodeState::DOWN,
bollard::secret::NodeState::READY => NodeState::READY,
bollard::secret::NodeState::DISCONNECTED => {
NodeState::DISCONNECTED
}
}
}
+78
View File
@@ -0,0 +1,78 @@
use anyhow::Context;
use bollard::query_parameters::ListSecretsOptions;
use komodo_client::entities::docker::secret::{
SecretSpec, SwarmSecret, SwarmSecretListItem,
};
use super::DockerClient;
impl DockerClient {
pub async fn list_swarm_secrets(
&self,
) -> anyhow::Result<Vec<SwarmSecretListItem>> {
let secrets = self
.docker
.list_secrets(Option::<ListSecretsOptions>::None)
.await
.context("Failed to query for swarm secret list")?
.into_iter()
.map(convert_secret_list_item)
.collect();
Ok(secrets)
}
pub async fn inspect_swarm_secret(
&self,
secret_id: &str,
) -> anyhow::Result<SwarmSecret> {
self
.docker
.inspect_secret(secret_id)
.await
.map(convert_secret)
.with_context(|| {
format!(
"Failed to query for swarm secret with id {secret_id}"
)
})
}
}
fn convert_secret_list_item(
secret: bollard::models::Secret,
) -> SwarmSecretListItem {
let (name, driver, templating) = secret
.spec
.map(|spec| {
(
spec.name,
spec.driver.map(|driver| driver.name),
spec.templating.map(|driver| driver.name),
)
})
.unwrap_or_default();
SwarmSecretListItem {
id: secret.id,
name,
driver,
templating,
created_at: secret.created_at,
updated_at: secret.updated_at,
}
}
fn convert_secret(secret: bollard::models::Secret) -> SwarmSecret {
SwarmSecret {
id: secret.id,
version: secret.version.map(super::convert_object_version),
created_at: secret.created_at,
updated_at: secret.updated_at,
spec: secret.spec.map(|spec| SecretSpec {
name: spec.name,
labels: spec.labels,
data: spec.data,
driver: spec.driver.map(super::convert_driver),
templating: spec.templating.map(super::convert_driver),
}),
}
}
+227
View File
@@ -0,0 +1,227 @@
use anyhow::Context;
use bollard::query_parameters::{
InspectServiceOptions, ListServicesOptions,
};
use komodo_client::entities::docker::{
NetworkAttachmentConfig, service::*,
};
use super::*;
impl DockerClient {
/// List swarm services
pub async fn list_swarm_services(
&self,
) -> anyhow::Result<Vec<SwarmServiceListItem>> {
let services = self
.docker
.list_services(Option::<ListServicesOptions>::None)
.await
.context("Failed to query for swarm service list")?
.into_iter()
.map(convert_service_list_item)
.collect();
Ok(services)
}
pub async fn inspect_swarm_service(
&self,
service_name: &str,
) -> anyhow::Result<SwarmService> {
self
.docker
.inspect_service(
service_name,
Some(InspectServiceOptions {
insert_defaults: true,
}),
)
.await
.map(convert_service)
.with_context(|| {
format!(
"Failed to query for swarm service with name {service_name}"
)
})
}
}
fn convert_service_list_item(
service: bollard::models::Service,
) -> SwarmServiceListItem {
let (name, (image, restart, runtime), replicas) = service
.spec
.map(|spec| {
(
spec.name,
spec
.task_template
.map(|template| {
(
template.container_spec.and_then(|spec| spec.image),
template.restart_policy.and_then(|policy| {
policy
.condition
.map(convert_task_spec_restart_policy_condition)
}),
template.runtime,
)
})
.unwrap_or_default(),
spec.mode.and_then(|mode| {
mode.replicated.and_then(|replicated| replicated.replicas)
}),
)
})
.unwrap_or_default();
SwarmServiceListItem {
id: service.id,
name,
replicas,
image,
restart,
runtime,
created_at: service.created_at,
updated_at: service.updated_at,
}
}
fn convert_service(
service: bollard::models::Service,
) -> SwarmService {
SwarmService {
id: service.id,
version: service.version.map(convert_object_version),
created_at: service.created_at,
updated_at: service.updated_at,
spec: service.spec.map(|spec| ServiceSpec {
name: spec.name,
labels: spec.labels,
task_template: spec.task_template.map(convert_task_spec),
mode: spec.mode.map(|mode| ServiceSpecMode {
replicated: mode.replicated.map(|replicated| ServiceSpecModeReplicated {
replicas: replicated.replicas,
}),
// global: mode.global,
replicated_job: mode.replicated_job.map(|job| ServiceSpecModeReplicatedJob {
max_concurrent: job.max_concurrent,
total_completions: job.total_completions,
}),
// global_job: mode.global_job,
}),
update_config: spec.update_config.map(|config| {
ServiceSpecUpdateConfig {
parallelism: config.parallelism,
delay: config.delay,
failure_action: config.failure_action.map(|action| match action {
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::EMPTY => ServiceSpecUpdateConfigFailureActionEnum::EMPTY,
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::CONTINUE => ServiceSpecUpdateConfigFailureActionEnum::CONTINUE,
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::PAUSE => ServiceSpecUpdateConfigFailureActionEnum::PAUSE,
bollard::secret::ServiceSpecUpdateConfigFailureActionEnum::ROLLBACK => ServiceSpecUpdateConfigFailureActionEnum::ROLLBACK,
}),
monitor: config.monitor,
max_failure_ratio: config.max_failure_ratio,
order: config.order.map(|order| match order {
bollard::secret::ServiceSpecUpdateConfigOrderEnum::EMPTY => ServiceSpecUpdateConfigOrderEnum::EMPTY,
bollard::secret::ServiceSpecUpdateConfigOrderEnum::STOP_FIRST => ServiceSpecUpdateConfigOrderEnum::STOP_FIRST,
bollard::secret::ServiceSpecUpdateConfigOrderEnum::START_FIRST => ServiceSpecUpdateConfigOrderEnum::START_FIRST,
}),
}
}),
rollback_config: spec.rollback_config.map(|config| {
ServiceSpecRollbackConfig {
parallelism: config.parallelism,
delay: config.delay,
failure_action: config.failure_action.map(|action| match action {
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::EMPTY => ServiceSpecRollbackConfigFailureActionEnum::EMPTY,
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::CONTINUE => ServiceSpecRollbackConfigFailureActionEnum::CONTINUE,
bollard::secret::ServiceSpecRollbackConfigFailureActionEnum::PAUSE => ServiceSpecRollbackConfigFailureActionEnum::PAUSE,
}),
monitor: config.monitor,
max_failure_ratio: config.max_failure_ratio,
order: config.order.map(|order| match order {
bollard::secret::ServiceSpecRollbackConfigOrderEnum::EMPTY => ServiceSpecRollbackConfigOrderEnum::EMPTY,
bollard::secret::ServiceSpecRollbackConfigOrderEnum::STOP_FIRST => ServiceSpecRollbackConfigOrderEnum::STOP_FIRST,
bollard::secret::ServiceSpecRollbackConfigOrderEnum::START_FIRST => ServiceSpecRollbackConfigOrderEnum::START_FIRST,
}),
}
}),
networks: spec.networks.map(|networks| {
networks
.into_iter()
.map(|network| NetworkAttachmentConfig {
target: network.target,
aliases: network.aliases,
driver_opts: network.driver_opts,
})
.collect()
}),
endpoint_spec: spec.endpoint_spec.map(convert_endpoint_spec),
}),
endpoint: service.endpoint.map(|endpoint| ServiceEndpoint {
spec: endpoint.spec.map(convert_endpoint_spec),
ports: endpoint.ports.map(convert_endpoint_spec_ports),
virtual_ips: endpoint.virtual_ips.map(|ips| {
ips
.into_iter()
.map(|ip| ServiceEndpointVirtualIps {
network_id: ip.network_id,
addr: ip.addr,
})
.collect()
}),
}),
update_status: service.update_status.map(|status| {
ServiceUpdateStatus {
state: status.state.map(convert_state),
started_at: status.started_at,
completed_at: status.completed_at,
message: status.message,
}
}),
service_status: service.service_status.map(|status| {
ServiceServiceStatus {
running_tasks: status.running_tasks,
desired_tasks: status.desired_tasks,
completed_tasks: status.completed_tasks,
}
}),
job_status: service.job_status.map(|status| ServiceJobStatus {
job_iteration: status.job_iteration.map(convert_object_version),
last_execution: status.last_execution,
}),
}
}
fn convert_endpoint_spec(
spec: bollard::models::EndpointSpec,
) -> EndpointSpec {
EndpointSpec {
mode: spec.mode.map(|mode| match mode {
bollard::secret::EndpointSpecModeEnum::EMPTY => {
EndpointSpecModeEnum::EMPTY
}
bollard::secret::EndpointSpecModeEnum::VIP => {
EndpointSpecModeEnum::VIP
}
bollard::secret::EndpointSpecModeEnum::DNSRR => {
EndpointSpecModeEnum::DNSRR
}
}),
ports: spec.ports.map(convert_endpoint_spec_ports),
}
}
fn convert_state(
state: bollard::secret::ServiceUpdateStatusStateEnum,
) -> ServiceUpdateStatusStateEnum {
match state {
bollard::secret::ServiceUpdateStatusStateEnum::EMPTY => ServiceUpdateStatusStateEnum::EMPTY,
bollard::secret::ServiceUpdateStatusStateEnum::UPDATING => ServiceUpdateStatusStateEnum::UPDATING,
bollard::secret::ServiceUpdateStatusStateEnum::PAUSED => ServiceUpdateStatusStateEnum::PAUSED,
bollard::secret::ServiceUpdateStatusStateEnum::COMPLETED => ServiceUpdateStatusStateEnum::COMPLETED,
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_STARTED => ServiceUpdateStatusStateEnum::ROLLBACK_STARTED,
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_PAUSED => ServiceUpdateStatusStateEnum::ROLLBACK_PAUSED,
bollard::secret::ServiceUpdateStatusStateEnum::ROLLBACK_COMPLETED => ServiceUpdateStatusStateEnum::ROLLBACK_COMPLETED,
}
}
+93
View File
@@ -0,0 +1,93 @@
use anyhow::{Context, anyhow};
use command::run_komodo_standard_command;
use komodo_client::entities::docker::stack::{
SwarmStackListItem, SwarmStackLists, SwarmStackServiceListItem,
SwarmStackTaskListItem,
};
pub async fn inspect_swarm_stack(
name: String,
) -> anyhow::Result<SwarmStackLists> {
let (services, tasks) = tokio::try_join!(
list_swarm_stack_services(&name),
list_swarm_stack_tasks(&name),
)?;
Ok(SwarmStackLists {
name,
services,
tasks,
})
}
pub async fn list_swarm_stacks()
-> anyhow::Result<Vec<SwarmStackListItem>> {
let res = run_komodo_standard_command(
"List Swarm Stacks",
None,
"docker stack ls --format json",
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(
"Failed to list swarm stacks using 'docker stack ls'",
));
}
// The output is in JSONL, need to convert to standard JSON vec.
serde_json::from_str(&format!(
"[{}]",
res.stdout.trim().replace('\n', ",")
))
.context("Failed to parse 'docker stack ls' response from json")
}
pub async fn list_swarm_stack_services(
stack: &str,
) -> anyhow::Result<Vec<SwarmStackServiceListItem>> {
let res = run_komodo_standard_command(
"List Swarm Stack Services",
None,
format!("docker stack services --format json {stack}"),
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(
"Failed to list swarm stacks using 'docker stack services'",
));
}
// The output is in JSONL, need to convert to standard JSON vec.
serde_json::from_str(&format!(
"[{}]",
res.stdout.trim().replace('\n', ",")
))
.context(
"Failed to parse 'docker stack services' response from json",
)
}
pub async fn list_swarm_stack_tasks(
stack: &str,
) -> anyhow::Result<Vec<SwarmStackTaskListItem>> {
let res = run_komodo_standard_command(
"List Swarm Stack Tasks",
None,
format!("docker stack ps --format json {stack}"),
)
.await;
if !res.success {
return Err(anyhow!("{}", res.combined()).context(
"Failed to list swarm stacks using 'docker stack ps'",
));
}
// The output is in JSONL, need to convert to standard JSON vec.
serde_json::from_str(&format!(
"[{}]",
res.stdout.trim().replace('\n', ",")
))
.context("Failed to parse 'docker stack ps' response from json")
}
+96
View File
@@ -0,0 +1,96 @@
use anyhow::Context;
use komodo_client::entities::docker::swarm::{
JoinTokens, SwarmInspectInfo, SwarmSpec, SwarmSpecCaConfig,
SwarmSpecCaConfigExternalCas,
SwarmSpecCaConfigExternalCasProtocolEnum, SwarmSpecDispatcher,
SwarmSpecEncryptionConfig, SwarmSpecOrchestration, SwarmSpecRaft,
SwarmSpecTaskDefaults, SwarmSpecTaskDefaultsLogDriver,
};
use super::DockerClient;
impl DockerClient {
/// Inspect swarm info
pub async fn inspect_swarm(
&self,
) -> anyhow::Result<SwarmInspectInfo> {
self
.docker
.inspect_swarm()
.await
.map(convert_swarm_info)
.context("Failed to query for swarm info")
}
}
fn convert_swarm_info(
swarm: bollard::models::Swarm,
) -> SwarmInspectInfo {
SwarmInspectInfo {
id: swarm.id,
version: swarm.version.map(super::convert_object_version),
created_at: swarm.created_at,
updated_at: swarm.updated_at,
spec: swarm.spec.map(|spec| SwarmSpec {
name: spec.name,
labels: spec.labels,
orchestration: spec.orchestration.map(|orchestration| {
SwarmSpecOrchestration {
task_history_retention_limit: orchestration
.task_history_retention_limit,
}
}),
raft: spec.raft.map(|raft| SwarmSpecRaft {
snapshot_interval: raft.snapshot_interval,
keep_old_snapshots: raft.keep_old_snapshots,
log_entries_for_slow_followers: raft
.log_entries_for_slow_followers,
election_tick: raft.election_tick,
heartbeat_tick: raft.heartbeat_tick,
}),
dispatcher: spec.dispatcher.map(|dispatcher| {
SwarmSpecDispatcher {
heartbeat_period: dispatcher.heartbeat_period,
}
}),
ca_config: spec.ca_config.map(|config| SwarmSpecCaConfig {
node_cert_expiry: config.node_cert_expiry,
external_cas: config.external_cas.map(|cas| {
cas
.into_iter()
.map(|cas| SwarmSpecCaConfigExternalCas {
protocol: cas.protocol.map(|protocol| match protocol {
bollard::secret::SwarmSpecCaConfigExternalCasProtocolEnum::EMPTY => SwarmSpecCaConfigExternalCasProtocolEnum::EMPTY,
bollard::secret::SwarmSpecCaConfigExternalCasProtocolEnum::CFSSL => SwarmSpecCaConfigExternalCasProtocolEnum::CFSSL,
}),
url: cas.url,
options: cas.options,
ca_cert: cas.ca_cert,
})
.collect()
}),
signing_ca_cert: config.signing_ca_cert,
signing_ca_key: config.signing_ca_key,
force_rotate: config.force_rotate,
}),
encryption_config: spec.encryption_config.map(|config| SwarmSpecEncryptionConfig {
auto_lock_managers: config.auto_lock_managers,
}),
task_defaults: spec.task_defaults.map(|defaults| SwarmSpecTaskDefaults {
log_driver: defaults.log_driver.map(|driver| SwarmSpecTaskDefaultsLogDriver {
name: driver.name,
options: driver.options,
}),
}),
}),
tls_info: swarm.tls_info.map(super::convert_tls_info),
root_rotation_in_progress: swarm.root_rotation_in_progress,
data_path_port: swarm.data_path_port,
default_addr_pool: swarm.default_addr_pool,
subnet_size: swarm.subnet_size,
join_tokens: swarm.join_tokens.map(|tokens| JoinTokens {
worker: tokens.worker,
manager: tokens.manager,
}),
}
}
+120
View File
@@ -0,0 +1,120 @@
use anyhow::Context;
use bollard::query_parameters::ListTasksOptions;
use komodo_client::entities::docker::task::*;
use super::*;
impl DockerClient {
pub async fn list_swarm_tasks(
&self,
) -> anyhow::Result<Vec<SwarmTaskListItem>> {
let tasks = self
.docker
.list_tasks(Option::<ListTasksOptions>::None)
.await
.context("Failed to query for swarm tasks list")?
.into_iter()
.map(convert_task_list_item)
.collect();
Ok(tasks)
}
pub async fn inspect_swarm_task(
&self,
task_id: &str,
) -> anyhow::Result<SwarmTask> {
self
.docker
.inspect_task(task_id)
.await
.map(convert_task)
.with_context(|| {
format!("Failed to query for swarm task with id {task_id}")
})
}
}
fn convert_task_list_item(
task: bollard::models::Task,
) -> SwarmTaskListItem {
let (container_id, state) = task
.status
.map(|status| {
(
status
.container_status
.and_then(|status| status.container_id),
status.state.map(convert_task_state),
)
})
.unwrap_or_default();
SwarmTaskListItem {
id: task.id,
name: task.name,
node_id: task.node_id,
service_id: task.service_id,
container_id,
state,
desired_state: task.desired_state.map(convert_task_state),
created_at: task.created_at,
updated_at: task.updated_at,
}
}
fn convert_task(task: bollard::models::Task) -> SwarmTask {
SwarmTask {
id: task.id,
version: task.version.map(convert_object_version),
created_at: task.created_at,
updated_at: task.updated_at,
name: task.name,
labels: task.labels,
spec: task.spec.map(convert_task_spec),
service_id: task.service_id,
slot: task.slot,
node_id: task.node_id,
assigned_generic_resources: task
.assigned_generic_resources
.map(convert_generic_resources),
status: task.status.map(|status| TaskStatus {
timestamp: status.timestamp,
state: status.state.map(convert_task_state),
message: status.message,
err: status.err,
container_status: status.container_status.map(|status| {
ContainerStatus {
container_id: status.container_id,
pid: status.pid,
exit_code: status.exit_code,
}
}),
port_status: status.port_status.map(|status| PortStatus {
ports: status.ports.map(convert_endpoint_spec_ports),
}),
}),
desired_state: task.desired_state.map(convert_task_state),
job_iteration: task.job_iteration.map(convert_object_version),
}
}
fn convert_task_state(
state: bollard::models::TaskState,
) -> TaskState {
match state {
bollard::secret::TaskState::NEW => TaskState::NEW,
bollard::secret::TaskState::ALLOCATED => TaskState::ALLOCATED,
bollard::secret::TaskState::PENDING => TaskState::PENDING,
bollard::secret::TaskState::ASSIGNED => TaskState::ASSIGNED,
bollard::secret::TaskState::ACCEPTED => TaskState::ACCEPTED,
bollard::secret::TaskState::PREPARING => TaskState::PREPARING,
bollard::secret::TaskState::READY => TaskState::READY,
bollard::secret::TaskState::STARTING => TaskState::STARTING,
bollard::secret::TaskState::RUNNING => TaskState::RUNNING,
bollard::secret::TaskState::COMPLETE => TaskState::COMPLETE,
bollard::secret::TaskState::SHUTDOWN => TaskState::SHUTDOWN,
bollard::secret::TaskState::FAILED => TaskState::FAILED,
bollard::secret::TaskState::REJECTED => TaskState::REJECTED,
bollard::secret::TaskState::REMOVE => TaskState::REMOVE,
bollard::secret::TaskState::ORPHANED => TaskState::ORPHANED,
}
}
@@ -78,9 +78,7 @@ impl DockerClient {
cluster_volume: volume.cluster_volume.map(|volume| {
ClusterVolume {
id: volume.id,
version: volume.version.map(|version| ObjectVersion {
index: version.index,
}),
version: volume.version.map(super::convert_object_version),
created_at: volume.created_at,
updated_at: volume.updated_at,
spec: volume.spec.map(|spec| ClusterVolumeSpec {
@@ -100,8 +98,8 @@ impl DockerClient {
bollard::secret::ClusterVolumeSpecAccessModeSharingEnum::ALL => ClusterVolumeSpecAccessModeSharingEnum::All,
}).unwrap_or_default(),
secrets: mode.secrets.unwrap_or_default().into_iter().map(|secret| ClusterVolumeSpecAccessModeSecrets {
key: secret.key,
secret: secret.secret,
key: secret.key,
secret: secret.secret,
}).collect(),
accessibility_requirements: mode
.accessibility_requirements.map(|req| ClusterVolumeSpecAccessModeAccessibilityRequirements {
+19 -12
View File
@@ -1,5 +1,6 @@
use std::{
net::IpAddr, path::PathBuf, str::FromStr as _, sync::OnceLock,
time::Duration,
};
use anyhow::Context;
@@ -221,12 +222,10 @@ fn opendns_resolver() -> &'static OpenDNSResolver {
static OPENDNS_RESOLVER: OnceLock<OpenDNSResolver> =
OnceLock::new();
OPENDNS_RESOLVER.get_or_init(|| {
// OpenDNS resolver ips
// OpenDNS resolver ipv4s
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(),
IpAddr::from_str("208.67.222.222").unwrap(),
];
// trust_negative_responses=true means NXDOMAIN/empty NOERROR from an
@@ -249,15 +248,23 @@ fn opendns_resolver() -> &'static OpenDNSResolver {
})
}
/// Includes 1s timeout
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")
tokio::time::timeout(Duration::from_secs(1), async {
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")
})
.await
.context("OpenDNS call for public IP timed out")
.flatten()
}
// =====
+2
View File
@@ -18,6 +18,7 @@ mod repo;
mod schedule;
mod server;
mod stack;
mod swarm;
mod sync;
mod tag;
mod terminal;
@@ -42,6 +43,7 @@ pub use repo::*;
pub use schedule::*;
pub use server::*;
pub use stack::*;
pub use swarm::*;
pub use sync::*;
pub use tag::*;
pub use terminal::*;
+487
View File
@@ -0,0 +1,487 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{
SearchCombinator, U64,
docker::{
config::{SwarmConfig, SwarmConfigListItem},
node::{SwarmNode, SwarmNodeListItem},
secret::{SwarmSecret, SwarmSecretListItem},
service::{SwarmService, SwarmServiceListItem},
stack::{SwarmStackListItem, SwarmStackLists},
swarm::SwarmInspectInfo,
task::{SwarmTask, SwarmTaskListItem},
},
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmQuery},
update::Log,
};
use super::KomodoReadRequest;
//
/// Get a specific swarm. Response: [Swarm].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(Swarm)]
#[error(serror::Error)]
pub struct GetSwarm {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type GetSwarmResponse = Swarm;
//
/// List Swarms matching optional query. Response: [ListSwarmsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmsResponse)]
#[error(serror::Error)]
pub struct ListSwarms {
/// Optional structured query to filter Swarms.
#[serde(default)]
pub query: SwarmQuery,
}
#[typeshare]
pub type ListSwarmsResponse = Vec<SwarmListItem>;
//
/// List Swarms matching optional query. Response: [ListFullSwarmsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListFullSwarmsResponse)]
#[error(serror::Error)]
pub struct ListFullSwarms {
/// optional structured query to filter swarms.
#[serde(default)]
pub query: SwarmQuery,
}
#[typeshare]
pub type ListFullSwarmsResponse = Vec<Swarm>;
//
/// Get current action state for the swarm. Response: [SwarmActionState].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(GetSwarmActionStateResponse)]
#[error(serror::Error)]
pub struct GetSwarmActionState {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type GetSwarmActionStateResponse = SwarmActionState;
//
/// Gets a summary of data relating to all swarms.
/// Response: [GetSwarmsSummaryResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(GetSwarmsSummaryResponse)]
#[error(serror::Error)]
pub struct GetSwarmsSummary {}
/// Response for [GetSwarmsSummary]
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct GetSwarmsSummaryResponse {
/// The total number of Swarms
pub total: u32,
/// The number of Swarms with Healthy state.
pub healthy: u32,
/// The number of Swarms with Unhealthy state
pub unhealthy: u32,
/// The number of Swarms with Unknown state
pub unknown: u32,
}
//
/// Inspect information about the swarm.
/// Response: [SwarmInspectInfo].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmResponse)]
#[error(serror::Error)]
pub struct InspectSwarm {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type InspectSwarmResponse = SwarmInspectInfo;
//
/// List nodes part of the target Swarm.
/// Response: [ListSwarmNodesResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmNodesResponse)]
#[error(serror::Error)]
pub struct ListSwarmNodes {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmNodesResponse = Vec<SwarmNodeListItem>;
//
/// Inspect a Swarm node.
/// Response: [SwarmNode].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmNodeResponse)]
#[error(serror::Error)]
pub struct InspectSwarmNode {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Node id
pub node: String,
}
#[typeshare]
pub type InspectSwarmNodeResponse = SwarmNode;
//
/// List services on the target Swarm.
/// Response: [ListSwarmServicesResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmServicesResponse)]
#[error(serror::Error)]
pub struct ListSwarmServices {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmServicesResponse = Vec<SwarmServiceListItem>;
//
/// Inspect a Swarm service.
/// Response: [SwarmService].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmServiceResponse)]
#[error(serror::Error)]
pub struct InspectSwarmService {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Service id
pub service: String,
}
#[typeshare]
pub type InspectSwarmServiceResponse = SwarmService;
//
/// Get a swarm service's logs. Response: [GetSwarmServiceLogResponse].
///
/// Note. This call will hit the underlying server directly for most up to date log.
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(GetSwarmServiceLogResponse)]
#[error(serror::Error)]
pub struct GetSwarmServiceLog {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Select the swarm service to get logs for.
pub service: String,
/// The number of lines of the log tail to include.
/// Default: 100.
/// Max: 5000.
#[serde(default = "default_tail")]
pub tail: U64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
/// Enable `--no-task-ids`
#[serde(default)]
pub no_task_ids: bool,
/// Enable `--no-resolve`
#[serde(default)]
pub no_resolve: bool,
/// Enable `--details`
#[serde(default)]
pub details: bool,
}
fn default_tail() -> u64 {
50
}
#[typeshare]
pub type GetSwarmServiceLogResponse = Log;
//
/// Search the swarm service log's tail using `grep`. All lines go to stdout.
/// Response: [SearchSwarmServiceLogResponse].
///
/// Note. This call will hit the underlying server directly for most up to date log.
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(SearchSwarmServiceLogResponse)]
#[error(serror::Error)]
pub struct SearchSwarmServiceLog {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Select the swarm service to get logs for.
pub service: String,
/// The terms to search for.
pub terms: Vec<String>,
/// When searching for multiple terms, can use `AND` or `OR` combinator.
///
/// - `AND`: Only include lines with **all** terms present in that line.
/// - `OR`: Include lines that have one or more matches in the terms.
#[serde(default)]
pub combinator: SearchCombinator,
/// Invert the results, ie return all lines that DON'T match the terms / combinator.
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
/// Enable `--no-task-ids`
#[serde(default)]
pub no_task_ids: bool,
/// Enable `--no-resolve`
#[serde(default)]
pub no_resolve: bool,
/// Enable `--details`
#[serde(default)]
pub details: bool,
}
#[typeshare]
pub type SearchSwarmServiceLogResponse = Log;
//
/// List tasks on the target Swarm.
/// Response: [ListSwarmTasksResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmTasksResponse)]
#[error(serror::Error)]
pub struct ListSwarmTasks {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmTasksResponse = Vec<SwarmTaskListItem>;
//
/// Inspect a Swarm task.
/// Response: [SwarmTask].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmTaskResponse)]
#[error(serror::Error)]
pub struct InspectSwarmTask {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Task id
pub task: String,
}
#[typeshare]
pub type InspectSwarmTaskResponse = SwarmTask;
//
/// List secrets on the target Swarm.
/// Response: [ListSwarmSecretsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmSecretsResponse)]
#[error(serror::Error)]
pub struct ListSwarmSecrets {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmSecretsResponse = Vec<SwarmSecretListItem>;
//
/// Inspect a Swarm secret.
/// Response: [SwarmSecret].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmSecretResponse)]
#[error(serror::Error)]
pub struct InspectSwarmSecret {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Secret id
pub secret: String,
}
#[typeshare]
pub type InspectSwarmSecretResponse = SwarmSecret;
//
/// List configs on the target Swarm.
/// Response: [ListSwarmConfigsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmConfigsResponse)]
#[error(serror::Error)]
pub struct ListSwarmConfigs {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmConfigsResponse = Vec<SwarmConfigListItem>;
//
/// Inspect a config on the target Swarm.
/// Response: [InspectSwarmConfigResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmConfigResponse)]
#[error(serror::Error)]
pub struct InspectSwarmConfig {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Swarm config ID or Name
pub config: String,
}
#[typeshare]
pub type InspectSwarmConfigResponse = Vec<SwarmConfig>;
//
/// List stacks on the target Swarm.
/// Response: [ListSwarmStacksResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListSwarmStacksResponse)]
#[error(serror::Error)]
pub struct ListSwarmStacks {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
}
#[typeshare]
pub type ListSwarmStacksResponse = Vec<SwarmStackListItem>;
//
/// Inspect a stack on the target Swarm.
/// Response: [SwarmStackLists].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(InspectSwarmStackResponse)]
#[error(serror::Error)]
pub struct InspectSwarmStack {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub swarm: String,
/// Swarm stack name
pub stack: String,
}
#[typeshare]
pub type InspectSwarmStackResponse = SwarmStackLists;
+2
View File
@@ -13,6 +13,7 @@ mod repo;
mod resource;
mod server;
mod stack;
mod swarm;
mod sync;
mod tags;
mod terminal;
@@ -35,6 +36,7 @@ pub use repo::*;
pub use resource::*;
pub use server::*;
pub use stack::*;
pub use swarm::*;
pub use sync::*;
pub use tags::*;
pub use terminal::*;
+108
View File
@@ -0,0 +1,108 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{
swarm::{_PartialSwarmConfig, Swarm},
update::Update,
};
use super::KomodoWriteRequest;
//
/// Create a Swarm. Response: [Swarm].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(Swarm)]
#[error(serror::Error)]
pub struct CreateSwarm {
/// The name given to newly created swarm.
pub name: String,
/// Optional partial config to initialize the swarm with.
#[serde(default)]
pub config: _PartialSwarmConfig,
}
//
/// Creates a new Swarm with given `name` and the configuration
/// of the Swarm at the given `id`. Response: [Swarm].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(Swarm)]
#[error(serror::Error)]
pub struct CopySwarm {
/// The name of the new swarm.
pub name: String,
/// The id of the swarm to copy.
pub id: String,
}
//
/// Deletes the Swarm at the given id, and returns the deleted Swarm.
/// Response: [Swarm]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(Swarm)]
#[error(serror::Error)]
pub struct DeleteSwarm {
/// The id or name of the swarm to delete.
pub id: String,
}
//
/// Update the Swarm at the given id, and return the updated Swarm.
/// Response: [Swarm].
///
/// Note. If the attached server for the Swarm changes,
/// the Swarm will be deleted / cleaned up on the old server.
///
/// Note. This method updates only the fields which are set in the [_PartialSwarmConfig],
/// effectively merging diffs into the final document.
/// This is helpful when multiple users are using
/// the same resources concurrently by ensuring no unintentional
/// field changes occur from out of date local state.
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(Swarm)]
#[error(serror::Error)]
pub struct UpdateSwarm {
/// The id of the swarm to update.
pub id: String,
/// The partial config update to apply.
pub config: _PartialSwarmConfig,
}
//
/// Rename the Swarm at id to the given name.
/// Response: [Update].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(Update)]
#[error(serror::Error)]
pub struct RenameSwarm {
/// The id or name of the Swarm to rename.
pub id: String,
/// The new name.
pub name: String,
}
+8 -1
View File
@@ -2,13 +2,20 @@ use crate::entities::{
action::ActionActionState, build::BuildActionState,
deployment::DeploymentActionState, procedure::ProcedureActionState,
repo::RepoActionState, server::ServerActionState,
stack::StackActionState, sync::ResourceSyncActionState,
stack::StackActionState, swarm::SwarmActionState,
sync::ResourceSyncActionState,
};
pub trait Busy {
fn busy(&self) -> bool;
}
impl Busy for SwarmActionState {
fn busy(&self) -> bool {
false
}
}
impl Busy for ServerActionState {
fn busy(&self) -> bool {
self.pruning_containers
+16 -15
View File
@@ -327,7 +327,7 @@ pub struct UrlBuilderConfig {
/// The address of the Periphery agent
#[serde(default = "default_address")]
#[builder(default = default_address())]
#[partial(default(default_address()))]
#[partial_default(default_address())]
pub address: String,
/// An expected public key associated with Periphery private key.
/// If empty, doesn't validate Periphery public key.
@@ -337,7 +337,7 @@ pub struct UrlBuilderConfig {
/// Whether to validate the Periphery tls certificates.
#[serde(default = "default_insecure_tls")]
#[builder(default = default_insecure_tls())]
#[partial(default(default_insecure_tls()))]
#[partial_default(default_insecure_tls())]
pub insecure_tls: bool,
/// Deprecated. Use private / public keys instead.
/// An optional override passkey to use
@@ -423,18 +423,6 @@ pub struct AwsBuilderConfig {
#[partial_default(aws_default_volume_gb())]
pub volume_gb: i32,
/// The port periphery will be running on.
/// Default: `8120`
#[serde(default = "default_port")]
#[builder(default = "default_port()")]
#[partial_default(default_port())]
pub port: i32,
#[serde(default = "default_use_https")]
#[builder(default = "default_use_https()")]
#[partial_default(default_use_https())]
pub use_https: bool,
/// The EC2 ami id to create.
/// The ami should have the periphery client configured to start on startup,
/// and should have the necessary github / dockerhub accounts configured.
@@ -473,14 +461,27 @@ pub struct AwsBuilderConfig {
#[builder(default)]
pub user_data: String,
/// The port periphery will be running on.
/// Default: `8120`
#[serde(default = "default_port")]
#[builder(default = "default_port()")]
#[partial_default(default_port())]
pub port: i32,
#[serde(default = "default_use_https")]
#[builder(default = "default_use_https()")]
#[partial_default(default_use_https())]
pub use_https: bool,
/// An expected public key associated with Periphery private key.
/// If empty, doesn't validate Periphery public key.
#[serde(default)]
pub periphery_public_key: String,
/// Whether to validate the Periphery tls certificates.
#[serde(default = "default_insecure_tls")]
#[builder(default = default_insecure_tls())]
#[partial(default(default_insecure_tls()))]
#[partial_default(default_insecure_tls())]
pub insecure_tls: bool,
/// Which git providers are available on the AMI
@@ -0,0 +1,78 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::*;
/// Swarm config list item.
/// Returned by `docker config ls --format json`
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmConfigListItem {
/// User-defined name of the config.
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
/// User-defined key/value metadata, formatted as a string:
/// `"lab1=val1,lab2=val2"`.
#[serde(rename = "Labels")]
pub labels: Option<String>,
}
/// Swarm config details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmConfig {
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
#[serde(rename = "Spec")]
pub spec: Option<ConfigSpec>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ConfigSpec {
/// User-defined name of the config.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
/// Data is the data to store as a config, formatted as a Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string.
/// It must be empty if the Driver field is set, in which case the data is loaded from an external secret store.
/// The maximum allowed size is 500KB, as defined in [MaxSecretSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize).
#[serde(rename = "Data")]
pub data: Option<String>,
/// Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template. If no driver is set, no templating is used.
#[serde(rename = "Templating")]
pub templating: Option<Driver>,
}
+6 -197
View File
@@ -7,7 +7,10 @@ use typeshare::typeshare;
use crate::entities::{I64, Usize};
use super::{ContainerConfig, GraphDriverData, PortBinding};
use super::{
ContainerConfig, GraphDriverData, Mount, MountTypeEnum,
PortBinding, ResourcesUlimits,
};
/// Container summary returned by container list apis.
#[typeshare]
@@ -15,7 +18,7 @@ use super::{ContainerConfig, GraphDriverData, PortBinding};
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ContainerListItem {
/// The Server which holds the container.
/// The Server which hosts the container.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_id: Option<String>,
/// The first name in Names, not including the initial '/'
@@ -543,7 +546,7 @@ pub struct HostConfig {
/// Specification for mounts to be added to the container.
#[serde(default, rename = "Mounts")]
pub mounts: Vec<ContainerMount>,
pub mounts: Vec<Mount>,
/// Initial console size, as an `[height, width]` array.
#[serde(default, rename = "ConsoleSize")]
@@ -728,24 +731,6 @@ pub struct DeviceRequest {
pub options: HashMap<String, String>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ResourcesUlimits {
/// Name of ulimit
#[serde(rename = "Name")]
pub name: Option<String>,
/// Soft limit
#[serde(rename = "Soft")]
pub soft: Option<I64>,
/// Hard limit
#[serde(rename = "Hard")]
pub hard: Option<I64>,
}
#[typeshare]
#[derive(
Debug,
@@ -826,182 +811,6 @@ pub enum RestartPolicyNameEnum {
OnFailure,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ContainerMount {
/// Container path.
#[serde(rename = "Target")]
pub target: Option<String>,
/// Mount source (e.g. a volume name, a host path).
#[serde(rename = "Source")]
pub source: Option<String>,
/// The mount type. Available types: - `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container. - `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed. - `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs. - `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container. - `cluster` a Swarm cluster volume
#[serde(default, rename = "Type")]
pub typ: MountTypeEnum,
/// Whether the mount should be read-only.
#[serde(rename = "ReadOnly")]
pub read_only: Option<bool>,
/// The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`.
#[serde(rename = "Consistency")]
pub consistency: Option<String>,
#[serde(rename = "BindOptions")]
pub bind_options: Option<MountBindOptions>,
#[serde(rename = "VolumeOptions")]
pub volume_options: Option<MountVolumeOptions>,
#[serde(rename = "TmpfsOptions")]
pub tmpfs_options: Option<MountTmpfsOptions>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
Eq,
Ord,
Default,
)]
pub enum MountTypeEnum {
#[default]
#[serde(rename = "")]
Empty,
#[serde(rename = "bind")]
Bind,
#[serde(rename = "volume")]
Volume,
#[serde(rename = "image")]
Image,
#[serde(rename = "tmpfs")]
Tmpfs,
#[serde(rename = "npipe")]
Npipe,
#[serde(rename = "cluster")]
Cluster,
}
/// Optional configuration for the `bind` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountBindOptions {
/// A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`.
#[serde(default, rename = "Propagation")]
pub propagation: MountBindOptionsPropagationEnum,
/// Disable recursive bind mount.
#[serde(rename = "NonRecursive")]
pub non_recursive: Option<bool>,
/// Create mount point on host if missing
#[serde(rename = "CreateMountpoint")]
pub create_mountpoint: Option<bool>,
/// Make the mount non-recursively read-only, but still leave the mount recursive (unless NonRecursive is set to `true` in conjunction). Addded in v1.44, before that version all read-only mounts were non-recursive by default. To match the previous behaviour this will default to `true` for clients on versions prior to v1.44.
#[serde(rename = "ReadOnlyNonRecursive")]
pub read_only_non_recursive: Option<bool>,
/// Raise an error if the mount cannot be made recursively read-only.
#[serde(rename = "ReadOnlyForceRecursive")]
pub read_only_force_recursive: Option<bool>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
Eq,
Ord,
Default,
)]
pub enum MountBindOptionsPropagationEnum {
#[default]
#[serde(rename = "")]
Empty,
#[serde(rename = "private")]
Private,
#[serde(rename = "rprivate")]
Rprivate,
#[serde(rename = "shared")]
Shared,
#[serde(rename = "rshared")]
Rshared,
#[serde(rename = "slave")]
Slave,
#[serde(rename = "rslave")]
Rslave,
}
/// Optional configuration for the `volume` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountVolumeOptions {
/// Populate volume with data from the target.
#[serde(rename = "NoCopy")]
pub no_copy: Option<bool>,
/// User-defined key/value metadata.
#[serde(default, rename = "Labels")]
pub labels: HashMap<String, String>,
#[serde(rename = "DriverConfig")]
pub driver_config: Option<MountVolumeOptionsDriverConfig>,
/// Source path inside the volume. Must be relative without any back traversals.
#[serde(rename = "Subpath")]
pub subpath: Option<String>,
}
/// Map of driver specific options
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountVolumeOptionsDriverConfig {
/// Name of the driver to use to create the volume.
#[serde(rename = "Name")]
pub name: Option<String>,
/// key/value map of driver specific options.
#[serde(default, rename = "Options")]
pub options: HashMap<String, String>,
}
/// Optional configuration for the `tmpfs` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountTmpfsOptions {
/// The size for the tmpfs mount in bytes.
#[serde(rename = "SizeBytes")]
pub size_bytes: Option<I64>,
/// The permission mode for the tmpfs mount in an integer.
#[serde(rename = "Mode")]
pub mode: Option<I64>,
}
#[typeshare]
#[derive(
Debug,
+447 -1
View File
@@ -3,14 +3,56 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::I64;
use crate::entities::{
docker::{
config::SwarmConfigListItem, container::ContainerListItem,
image::ImageListItem, network::NetworkListItem,
node::SwarmNodeListItem, secret::SwarmSecretListItem,
service::SwarmServiceListItem, stack::SwarmStackListItem,
task::SwarmTaskListItem, volume::VolumeListItem,
},
stack::ComposeProject,
};
use super::{I64, U64};
pub mod config;
pub mod container;
pub mod image;
pub mod network;
pub mod node;
pub mod secret;
pub mod service;
pub mod stack;
pub mod stats;
pub mod swarm;
pub mod task;
pub mod volume;
/// Swarm lists available from a manager node.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwarmLists {
pub nodes: Vec<SwarmNodeListItem>,
pub services: Vec<SwarmServiceListItem>,
pub tasks: Vec<SwarmTaskListItem>,
pub secrets: Vec<SwarmSecretListItem>,
pub configs: Vec<SwarmConfigListItem>,
pub stacks: Vec<SwarmStackListItem>,
}
/// Standard docker lists available from a Server.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct DockerLists {
pub containers: Vec<ContainerListItem>,
pub networks: Vec<NetworkListItem>,
pub images: Vec<ImageListItem>,
pub volumes: Vec<VolumeListItem>,
pub projects: Vec<ComposeProject>,
}
/// PortBinding represents a binding between a host IP address and a host port.
#[typeshare]
#[derive(
@@ -176,3 +218,407 @@ pub struct HealthConfig {
#[serde(rename = "StartInterval")]
pub start_interval: Option<I64>,
}
/// The version number of the object such as node, service, etc. This is needed to avoid conflicting writes. The client must send the version number along with the modified specification when updating these objects. This approach ensures safe concurrency and determinism in that the change on the object may not be applied if the version number has changed from the last read. In other words, if two update requests specify the same base version, only one of the requests can succeed. As a result, two separate update requests that happen at the same time will not unintentionally overwrite each other.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ObjectVersion {
#[serde(rename = "Index")]
pub index: Option<U64>,
}
/// Driver represents a driver (network, logging, secrets).
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct Driver {
/// Name of the driver.
#[serde(rename = "Name")]
pub name: String,
/// Key/value map of driver-specific options.
#[serde(rename = "Options")]
pub options: Option<HashMap<String, String>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct Mount {
/// Container path.
#[serde(rename = "Target")]
pub target: Option<String>,
/// Mount source (e.g. a volume name, a host path).
#[serde(rename = "Source")]
pub source: Option<String>,
/// The mount type. Available types:
/// - `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.
/// - `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.
/// - `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs. - `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.
/// - `cluster` a Swarm cluster volume
#[serde(default, rename = "Type")]
pub typ: MountTypeEnum,
/// Whether the mount should be read-only.
#[serde(rename = "ReadOnly")]
pub read_only: Option<bool>,
/// The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`.
#[serde(rename = "Consistency")]
pub consistency: Option<String>,
#[serde(rename = "BindOptions")]
pub bind_options: Option<MountBindOptions>,
#[serde(rename = "VolumeOptions")]
pub volume_options: Option<MountVolumeOptions>,
#[serde(rename = "TmpfsOptions")]
pub tmpfs_options: Option<MountTmpfsOptions>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
Eq,
Ord,
Default,
)]
pub enum MountTypeEnum {
#[default]
#[serde(rename = "")]
Empty,
#[serde(rename = "bind")]
Bind,
#[serde(rename = "volume")]
Volume,
#[serde(rename = "image")]
Image,
#[serde(rename = "tmpfs")]
Tmpfs,
#[serde(rename = "npipe")]
Npipe,
#[serde(rename = "cluster")]
Cluster,
}
/// Optional configuration for the `bind` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountBindOptions {
/// A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`.
#[serde(default, rename = "Propagation")]
pub propagation: MountBindOptionsPropagationEnum,
/// Disable recursive bind mount.
#[serde(rename = "NonRecursive")]
pub non_recursive: Option<bool>,
/// Create mount point on host if missing
#[serde(rename = "CreateMountpoint")]
pub create_mountpoint: Option<bool>,
/// Make the mount non-recursively read-only, but still leave the mount recursive (unless NonRecursive is set to `true` in conjunction). Addded in v1.44, before that version all read-only mounts were non-recursive by default. To match the previous behaviour this will default to `true` for clients on versions prior to v1.44.
#[serde(rename = "ReadOnlyNonRecursive")]
pub read_only_non_recursive: Option<bool>,
/// Raise an error if the mount cannot be made recursively read-only.
#[serde(rename = "ReadOnlyForceRecursive")]
pub read_only_force_recursive: Option<bool>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
Eq,
Ord,
Default,
)]
pub enum MountBindOptionsPropagationEnum {
#[default]
#[serde(rename = "")]
Empty,
#[serde(rename = "private")]
Private,
#[serde(rename = "rprivate")]
Rprivate,
#[serde(rename = "shared")]
Shared,
#[serde(rename = "rshared")]
Rshared,
#[serde(rename = "slave")]
Slave,
#[serde(rename = "rslave")]
Rslave,
}
/// Optional configuration for the `volume` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountVolumeOptions {
/// Populate volume with data from the target.
#[serde(rename = "NoCopy")]
pub no_copy: Option<bool>,
/// User-defined key/value metadata.
#[serde(default, rename = "Labels")]
pub labels: HashMap<String, String>,
#[serde(rename = "DriverConfig")]
pub driver_config: Option<MountVolumeOptionsDriverConfig>,
/// Source path inside the volume. Must be relative without any back traversals.
#[serde(rename = "Subpath")]
pub subpath: Option<String>,
}
/// Map of driver specific options
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountVolumeOptionsDriverConfig {
/// Name of the driver to use to create the volume.
#[serde(rename = "Name")]
pub name: Option<String>,
/// key/value map of driver specific options.
#[serde(default, rename = "Options")]
pub options: HashMap<String, String>,
}
/// Optional configuration for the `tmpfs` type.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct MountTmpfsOptions {
/// The size for the tmpfs mount in bytes.
#[serde(rename = "SizeBytes")]
pub size_bytes: Option<I64>,
/// The permission mode for the tmpfs mount in an integer.
#[serde(rename = "Mode")]
pub mode: Option<I64>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ResourcesUlimits {
/// Name of ulimit
#[serde(rename = "Name")]
pub name: Option<String>,
/// Soft limit
#[serde(rename = "Soft")]
pub soft: Option<I64>,
/// Hard limit
#[serde(rename = "Hard")]
pub hard: Option<I64>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ResourceObject {
#[serde(rename = "NanoCPUs")]
pub nano_cpus: Option<I64>,
#[serde(rename = "MemoryBytes")]
pub memory_bytes: Option<I64>,
#[serde(rename = "GenericResources")]
pub generic_resources: Option<GenericResources>,
}
/// User-defined resources can be either Integer resources (e.g, `SSD=3`) or String resources (e.g, `GPU=UUID1`).
#[typeshare]
pub type GenericResources = Vec<GenericResourcesInner>;
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct GenericResourcesInner {
#[serde(rename = "NamedResourceSpec")]
pub named_resource_spec:
Option<GenericResourcesInnerNamedResourceSpec>,
#[serde(rename = "DiscreteResourceSpec")]
pub discrete_resource_spec:
Option<GenericResourcesInnerDiscreteResourceSpec>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct GenericResourcesInnerDiscreteResourceSpec {
#[serde(rename = "Kind")]
pub kind: Option<String>,
#[serde(rename = "Value")]
pub value: Option<I64>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct GenericResourcesInnerNamedResourceSpec {
#[serde(rename = "Kind")]
pub kind: Option<String>,
#[serde(rename = "Value")]
pub value: Option<String>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct Platform {
/// Architecture represents the hardware architecture (for example, `x86_64`).
#[serde(rename = "Architecture")]
pub architecture: Option<String>,
/// OS represents the Operating System (for example, `linux` or `windows`).
#[serde(rename = "OS")]
pub os: Option<String>,
}
/// Specifies how a service should be attached to a particular network.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct NetworkAttachmentConfig {
/// The target network for attachment. Must be a network name or ID.
#[serde(rename = "Target")]
pub target: Option<String>,
/// Discoverable alternate names for the service on this network.
#[serde(rename = "Aliases")]
pub aliases: Option<Vec<String>>,
/// Driver attachment options for the network target.
#[serde(rename = "DriverOpts")]
pub driver_opts: Option<HashMap<String, String>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct EndpointPortConfig {
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "Protocol")]
pub protocol: Option<EndpointPortConfigProtocolEnum>,
/// The port inside the container.
#[serde(rename = "TargetPort")]
pub target_port: Option<I64>,
/// The port on the swarm hosts.
#[serde(rename = "PublishedPort")]
pub published_port: Option<I64>,
/// The mode in which port is published. <p><br /></p> - \"ingress\" makes the target port accessible on every node, regardless of whether there is a task for the service running on that node or not. - \"host\" bypasses the routing mesh and publish the port directly on the swarm node where that service is running.
#[serde(rename = "PublishMode")]
pub publish_mode: Option<EndpointPortConfigPublishModeEnum>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum EndpointPortConfigProtocolEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "tcp")]
TCP,
#[serde(rename = "udp")]
UDP,
#[serde(rename = "sctp")]
SCTP,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum EndpointPortConfigPublishModeEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "ingress")]
INGRESS,
#[serde(rename = "host")]
HOST,
}
/// Information about the issuer of leaf TLS certificates and the trusted root CA certificate.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TlsInfo {
/// The root CA certificate(s) that are used to validate leaf TLS certificates.
#[serde(rename = "TrustRoot")]
pub trust_root: Option<String>,
/// The base64-url-safe-encoded raw subject bytes of the issuer.
#[serde(rename = "CertIssuerSubject")]
pub cert_issuer_subject: Option<String>,
/// The base64-url-safe-encoded raw public key bytes of the issuer.
#[serde(rename = "CertIssuerPublicKey")]
pub cert_issuer_public_key: Option<String>,
}
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NetworkListItem {
pub name: Option<String>,
pub id: Option<String>,
+281
View File
@@ -0,0 +1,281 @@
//! Docker Swarm Node
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::*;
/// Swarm node list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmNodeListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// Name for the node.
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "Hostname")]
pub hostname: Option<String>,
/// Role of the node.
#[serde(rename = "Role")]
pub role: Option<NodeSpecRoleEnum>,
/// Availability of the node.
#[serde(rename = "Availability")]
pub availability: Option<NodeSpecAvailabilityEnum>,
/// State of the node
#[serde(rename = "State")]
pub state: Option<NodeState>,
/// Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
/// Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm node details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmNode {
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
/// Date and time at which the node was added to the swarm in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
/// Date and time at which the node was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
#[serde(rename = "Spec")]
pub spec: Option<NodeSpec>,
#[serde(rename = "Description")]
pub description: Option<NodeDescription>,
#[serde(rename = "Status")]
pub status: Option<NodeStatus>,
#[serde(rename = "ManagerStatus")]
pub manager_status: Option<ManagerStatus>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct NodeSpec {
/// Name for the node.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
/// Role of the node.
#[serde(rename = "Role")]
pub role: Option<NodeSpecRoleEnum>,
/// Availability of the node.
#[serde(rename = "Availability")]
pub availability: Option<NodeSpecAvailabilityEnum>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum NodeSpecRoleEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "worker")]
WORKER,
#[serde(rename = "manager")]
MANAGER,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum NodeSpecAvailabilityEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "active")]
ACTIVE,
#[serde(rename = "pause")]
PAUSE,
#[serde(rename = "drain")]
DRAIN,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct NodeDescription {
#[serde(rename = "Hostname")]
pub hostname: Option<String>,
#[serde(rename = "Platform")]
pub platform: Option<Platform>,
#[serde(rename = "Resources")]
pub resources: Option<ResourceObject>,
#[serde(rename = "Engine")]
pub engine: Option<EngineDescription>,
#[serde(rename = "TLSInfo")]
pub tls_info: Option<TlsInfo>,
}
/// EngineDescription provides information about an engine.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct EngineDescription {
#[serde(rename = "EngineVersion")]
pub engine_version: Option<String>,
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
#[serde(rename = "Plugins")]
pub plugins: Option<Vec<EngineDescriptionPlugins>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct EngineDescriptionPlugins {
#[serde(rename = "Type")]
pub typ: Option<String>,
#[serde(rename = "Name")]
pub name: Option<String>,
}
/// NodeStatus represents the status of a node. It provides the current status of the node, as seen by the manager.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct NodeStatus {
#[serde(rename = "State")]
pub state: Option<NodeState>,
#[serde(rename = "Message")]
pub message: Option<String>,
/// IP address of the node.
#[serde(rename = "Addr")]
pub addr: Option<String>,
}
/// NodeState represents the state of a node.
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum NodeState {
#[default]
#[serde(rename = "unknown")]
UNKNOWN,
#[serde(rename = "down")]
DOWN,
#[serde(rename = "ready")]
READY,
#[serde(rename = "disconnected")]
DISCONNECTED,
}
/// ManagerStatus represents the status of a manager. It provides the current status of a node's manager component, if the node is a manager.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ManagerStatus {
#[serde(rename = "Leader")]
pub leader: Option<bool>,
#[serde(rename = "Reachability")]
pub reachability: Option<NodeReachability>,
/// The IP address and port at which the manager is reachable.
#[serde(rename = "Addr")]
pub addr: Option<String>,
}
/// Reachability represents the reachability of a node.
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum NodeReachability {
#[default]
#[serde(rename = "unknown")]
UNKNOWN,
#[serde(rename = "unreachable")]
UNREACHABLE,
#[serde(rename = "reachable")]
REACHABLE,
}
@@ -0,0 +1,87 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::*;
/// Swarm secret list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSecretListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// User-defined name of the secret.
#[serde(rename = "Name")]
pub name: Option<String>,
/// Name of the secrets driver used to fetch the secret's value from an external secret store.
#[serde(rename = "Driver")]
pub driver: Option<String>,
/// Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template.
/// If no driver is set, no templating is used.
#[serde(rename = "Templating")]
pub templating: Option<String>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm secret details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSecret {
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
#[serde(rename = "Spec")]
pub spec: Option<SecretSpec>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SecretSpec {
/// User-defined name of the secret.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
/// Data is the data to store as a secret, formatted as a Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string.
/// It must be empty if the Driver field is set, in which case the data is loaded from an external secret store.
/// The maximum allowed size is 500KB, as defined in [MaxSecretSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize).
/// This field is only used to _create_ a secret, and is not returned by other endpoints.
#[serde(rename = "Data")]
pub data: Option<String>,
/// Name of the secrets driver used to fetch the secret's value from an external secret store.
#[serde(rename = "Driver")]
pub driver: Option<Driver>,
/// Templating driver, if applicable Templating controls whether and how to evaluate the config payload as a template.
/// If no driver is set, no templating is used.
#[serde(rename = "Templating")]
pub templating: Option<Driver>,
}
@@ -0,0 +1,473 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{
I64, U64, docker::task::TaskSpecRestartPolicyConditionEnum,
};
use super::{
EndpointPortConfig, NetworkAttachmentConfig, ObjectVersion,
task::TaskSpec,
};
/// Swarm service list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmServiceListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// Name of the service.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The image associated with service
#[serde(rename = "Image")]
pub image: Option<String>,
/// Runtime is the type of runtime specified for the task executor.
#[serde(rename = "Runtime")]
pub runtime: Option<String>,
/// Condition for restart.
#[serde(rename = "Restart")]
pub restart: Option<TaskSpecRestartPolicyConditionEnum>,
/// Number of replicas
#[serde(rename = "Replicas")]
pub replicas: Option<I64>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm service details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmService {
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
#[serde(rename = "Spec")]
pub spec: Option<ServiceSpec>,
#[serde(rename = "Endpoint")]
pub endpoint: Option<ServiceEndpoint>,
#[serde(rename = "UpdateStatus")]
pub update_status: Option<ServiceUpdateStatus>,
#[serde(rename = "ServiceStatus")]
pub service_status: Option<ServiceServiceStatus>,
#[serde(rename = "JobStatus")]
pub job_status: Option<ServiceJobStatus>,
}
/// User modifiable configuration for a service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpec {
/// Name of the service.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
#[serde(rename = "TaskTemplate")]
pub task_template: Option<TaskSpec>,
#[serde(rename = "Mode")]
pub mode: Option<ServiceSpecMode>,
#[serde(rename = "UpdateConfig")]
pub update_config: Option<ServiceSpecUpdateConfig>,
#[serde(rename = "RollbackConfig")]
pub rollback_config: Option<ServiceSpecRollbackConfig>,
/// Specifies which networks the service should attach to. Deprecated: This field is deprecated since v1.44. The Networks field in TaskSpec should be used instead.
#[serde(rename = "Networks")]
pub networks: Option<Vec<NetworkAttachmentConfig>>,
#[serde(rename = "EndpointSpec")]
pub endpoint_spec: Option<EndpointSpec>,
}
/// Scheduling mode for the service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpecMode {
#[serde(rename = "Replicated")]
pub replicated: Option<ServiceSpecModeReplicated>,
// #[serde(rename = "Global")]
// pub global: Option<HashMap<(), ()>>,
#[serde(rename = "ReplicatedJob")]
pub replicated_job: Option<ServiceSpecModeReplicatedJob>,
// /// The mode used for services which run a task to the completed state on each valid node.
// #[serde(rename = "GlobalJob")]
// pub global_job: Option<HashMap<(), ()>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpecModeReplicated {
#[serde(rename = "Replicas")]
pub replicas: Option<I64>,
}
/// The mode used for services with a finite number of tasks that run to a completed state.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpecModeReplicatedJob {
/// The maximum number of replicas to run simultaneously.
#[serde(rename = "MaxConcurrent")]
pub max_concurrent: Option<I64>,
/// The total number of replicas desired to reach the Completed state. If unset, will default to the value of `MaxConcurrent`
#[serde(rename = "TotalCompletions")]
pub total_completions: Option<I64>,
}
/// Specification for the update strategy of the service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpecUpdateConfig {
/// Maximum number of tasks to be updated in one iteration (0 means unlimited parallelism).
#[serde(rename = "Parallelism")]
pub parallelism: Option<I64>,
/// Amount of time between updates, in nanoseconds.
#[serde(rename = "Delay")]
pub delay: Option<I64>,
/// Action to take if an updated task fails to run, or stops running during the update.
#[serde(rename = "FailureAction")]
pub failure_action:
Option<ServiceSpecUpdateConfigFailureActionEnum>,
/// Amount of time to monitor each updated task for failures, in nanoseconds.
#[serde(rename = "Monitor")]
pub monitor: Option<I64>,
/// The fraction of tasks that may fail during an update before the failure action is invoked, specified as a floating point number between 0 and 1.
#[serde(rename = "MaxFailureRatio")]
pub max_failure_ratio: Option<f64>,
/// The order of operations when rolling out an updated task. Either the old task is shut down before the new task is started, or the new task is started before the old task is shut down.
#[serde(rename = "Order")]
pub order: Option<ServiceSpecUpdateConfigOrderEnum>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum ServiceSpecUpdateConfigFailureActionEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "continue")]
CONTINUE,
#[serde(rename = "pause")]
PAUSE,
#[serde(rename = "rollback")]
ROLLBACK,
}
#[allow(non_camel_case_types)]
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum ServiceSpecUpdateConfigOrderEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "stop-first")]
STOP_FIRST,
#[serde(rename = "start-first")]
START_FIRST,
}
/// Specification for the rollback strategy of the service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceSpecRollbackConfig {
/// Maximum number of tasks to be rolled back in one iteration (0 means unlimited parallelism).
#[serde(rename = "Parallelism")]
pub parallelism: Option<I64>,
/// Amount of time between rollback iterations, in nanoseconds.
#[serde(rename = "Delay")]
pub delay: Option<I64>,
/// Action to take if an rolled back task fails to run, or stops running during the rollback.
#[serde(rename = "FailureAction")]
pub failure_action:
Option<ServiceSpecRollbackConfigFailureActionEnum>,
/// Amount of time to monitor each rolled back task for failures, in nanoseconds.
#[serde(rename = "Monitor")]
pub monitor: Option<I64>,
/// The fraction of tasks that may fail during a rollback before the failure action is invoked, specified as a floating point number between 0 and 1.
#[serde(rename = "MaxFailureRatio")]
pub max_failure_ratio: Option<f64>,
/// The order of operations when rolling back a task. Either the old task is shut down before the new task is started, or the new task is started before the old task is shut down.
#[serde(rename = "Order")]
pub order: Option<ServiceSpecRollbackConfigOrderEnum>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum ServiceSpecRollbackConfigFailureActionEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "continue")]
CONTINUE,
#[serde(rename = "pause")]
PAUSE,
}
#[allow(non_camel_case_types)]
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum ServiceSpecRollbackConfigOrderEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "stop-first")]
STOP_FIRST,
#[serde(rename = "start-first")]
START_FIRST,
}
/// Properties that can be configured to access and load balance a service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct EndpointSpec {
/// The mode of resolution to use for internal load balancing between tasks.
#[serde(rename = "Mode")]
pub mode: Option<EndpointSpecModeEnum>,
/// List of exposed ports that this service is accessible on from the outside. Ports can only be provided if `vip` resolution mode is used.
#[serde(rename = "Ports")]
pub ports: Option<Vec<EndpointPortConfig>>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum EndpointSpecModeEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "vip")]
VIP,
#[serde(rename = "dnsrr")]
DNSRR,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceEndpoint {
#[serde(rename = "Spec")]
pub spec: Option<EndpointSpec>,
#[serde(rename = "Ports")]
pub ports: Option<Vec<EndpointPortConfig>>,
#[serde(rename = "VirtualIPs")]
pub virtual_ips: Option<Vec<ServiceEndpointVirtualIps>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceEndpointVirtualIps {
#[serde(rename = "NetworkID")]
pub network_id: Option<String>,
#[serde(rename = "Addr")]
pub addr: Option<String>,
}
/// The status of a service update.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceUpdateStatus {
#[serde(rename = "State")]
pub state: Option<ServiceUpdateStatusStateEnum>,
#[serde(rename = "StartedAt")]
pub started_at: Option<String>,
#[serde(rename = "CompletedAt")]
pub completed_at: Option<String>,
#[serde(rename = "Message")]
pub message: Option<String>,
}
#[allow(non_camel_case_types)]
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum ServiceUpdateStatusStateEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "updating")]
UPDATING,
#[serde(rename = "paused")]
PAUSED,
#[serde(rename = "completed")]
COMPLETED,
#[serde(rename = "rollback_started")]
ROLLBACK_STARTED,
#[serde(rename = "rollback_paused")]
ROLLBACK_PAUSED,
#[serde(rename = "rollback_completed")]
ROLLBACK_COMPLETED,
}
/// The status of the service's tasks. Provided only when requested as part of a ServiceList operation.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceServiceStatus {
/// The number of tasks for the service currently in the Running state.
#[serde(rename = "RunningTasks")]
pub running_tasks: Option<U64>,
/// The number of tasks for the service desired to be running.
/// For replicated services, this is the replica count from the service spec.
/// For global services, this is computed by taking count of all tasks for the service with a Desired State other than Shutdown.
#[serde(rename = "DesiredTasks")]
pub desired_tasks: Option<U64>,
/// The number of tasks for a job that are in the Completed state.
/// This field must be cross-referenced with the service type, as the value of 0 may mean the service is not in a job mode,
/// or it may mean the job-mode service has no tasks yet Completed.
#[serde(rename = "CompletedTasks")]
pub completed_tasks: Option<U64>,
}
/// The status of the service when it is in one of ReplicatedJob or GlobalJob modes. Absent on Replicated and Global mode services. The JobIteration is an ObjectVersion, but unlike the Service's version, does not need to be sent with an update request.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ServiceJobStatus {
/// JobIteration is a value increased each time a Job is executed, successfully or otherwise. \"Executed\", in this case, means the job as a whole has been started, not that an individual Task has been launched. A job is \"Executed\" when its ServiceSpec is updated. JobIteration can be used to disambiguate Tasks belonging to different executions of a job. Though JobIteration will increase with each subsequent execution, it may not necessarily increase by 1, and so JobIteration should not be used to
#[serde(rename = "JobIteration")]
pub job_iteration: Option<ObjectVersion>,
/// The last time, as observed by the server, that this job was started.
#[serde(rename = "LastExecution")]
pub last_execution: Option<String>,
}
+124
View File
@@ -0,0 +1,124 @@
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
/// Swarm stack list item.
/// Returned by `docker stack ls --format json`
///
/// https://docs.docker.com/reference/cli/docker/stack/ls/#format
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmStackListItem {
/// Swarm stack name.
#[serde(rename = "Name")]
pub name: Option<String>,
/// Number of services which are part of the stack
#[serde(rename = "Services")]
pub services: Option<String>,
/// The stack orchestrator
#[serde(rename = "Orchestrator")]
pub orchestrator: Option<String>,
/// The stack namespace
#[serde(rename = "Namespace")]
pub namespace: Option<String>,
}
/// All entities related to docker stack available over CLI.
/// Returned by:
/// ```
/// docker stack services --format json <STACK>
/// docker stack ps --format json <STACK>
/// ```
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmStackLists {
/// Swarm stack name.
#[serde(rename = "Name")]
pub name: String,
/// Services part of the stack
#[serde(rename = "Services")]
pub services: Vec<SwarmStackServiceListItem>,
/// Tasks part of the stack
#[serde(rename = "Tasks")]
pub tasks: Vec<SwarmStackTaskListItem>,
}
/// Swarm stack service list item.
/// Returned by `docker stack services --format json <NAME>`
///
/// https://docs.docker.com/reference/cli/docker/stack/services/#format
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmStackServiceListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// Swarm stack task name.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The service mode.
#[serde(rename = "Mode")]
pub mode: Option<String>,
/// The service replicas, formatted as string.
#[serde(rename = "Replicas")]
pub replicas: Option<String>,
/// The image associated with service
#[serde(rename = "Image")]
pub image: Option<String>,
/// Task exposed ports, formatted as a string.
#[serde(rename = "Ports")]
pub ports: Option<String>,
}
/// Swarm stack task list item.
/// Returned by `docker stack ps --format json <NAME>`
///
/// https://docs.docker.com/reference/cli/docker/stack/ps/#format
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmStackTaskListItem {
#[serde(rename = "ID")]
pub id: Option<String>,
/// Swarm stack task name.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The image associated with task
#[serde(rename = "Image")]
pub image: Option<String>,
/// The node the task is running on
#[serde(rename = "Node")]
pub node: Option<String>,
#[serde(rename = "DesiredState")]
pub desired_state: Option<String>,
#[serde(rename = "CurrentState")]
pub current_state: Option<String>,
/// An error message, if one exists
#[serde(rename = "Error")]
pub error: Option<String>,
/// Task exposed ports, formatted as a string.
#[serde(rename = "Ports")]
pub ports: Option<String>,
}
+257
View File
@@ -0,0 +1,257 @@
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::*;
/// Docker-level information about the Swarm.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmInspectInfo {
/// The (Docker) ID of the swarm.
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
/// Date and time at which the swarm was initialised in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
/// Date and time at which the swarm was last updated in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
#[serde(rename = "Spec")]
pub spec: Option<SwarmSpec>,
#[serde(rename = "TLSInfo")]
pub tls_info: Option<TlsInfo>,
/// Whether there is currently a root CA rotation in progress for the swarm
#[serde(rename = "RootRotationInProgress")]
pub root_rotation_in_progress: Option<bool>,
/// DataPathPort specifies the data path port number for data traffic. Acceptable port range is 1024 to 49151. If no port is set or is set to 0, the default port (4789) is used.
#[serde(rename = "DataPathPort")]
pub data_path_port: Option<u32>,
/// Default Address Pool specifies default subnet pools for global scope networks.
#[serde(rename = "DefaultAddrPool")]
pub default_addr_pool: Option<Vec<String>>,
/// SubnetSize specifies the subnet size of the networks created from the default subnet pool.
#[serde(rename = "SubnetSize")]
pub subnet_size: Option<u32>,
#[serde(rename = "JoinTokens")]
pub join_tokens: Option<JoinTokens>,
}
/// User modifiable swarm configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpec {
/// Name of the swarm.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
#[serde(rename = "Orchestration")]
pub orchestration: Option<SwarmSpecOrchestration>,
#[serde(rename = "Raft")]
pub raft: Option<SwarmSpecRaft>,
#[serde(rename = "Dispatcher")]
pub dispatcher: Option<SwarmSpecDispatcher>,
#[serde(rename = "CAConfig")]
pub ca_config: Option<SwarmSpecCaConfig>,
#[serde(rename = "EncryptionConfig")]
pub encryption_config: Option<SwarmSpecEncryptionConfig>,
#[serde(rename = "TaskDefaults")]
pub task_defaults: Option<SwarmSpecTaskDefaults>,
}
/// Orchestration configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecOrchestration {
/// The number of historic tasks to keep per instance or node.
/// If negative, never remove completed or failed tasks.
#[serde(rename = "TaskHistoryRetentionLimit")]
pub task_history_retention_limit: Option<I64>,
}
/// Raft configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecRaft {
/// The number of log entries between snapshots.
#[serde(rename = "SnapshotInterval")]
pub snapshot_interval: Option<U64>,
/// The number of snapshots to keep beyond the current snapshot.
#[serde(rename = "KeepOldSnapshots")]
pub keep_old_snapshots: Option<U64>,
/// The number of log entries to keep around to sync up slow followers after a snapshot is created.
#[serde(rename = "LogEntriesForSlowFollowers")]
pub log_entries_for_slow_followers: Option<U64>,
/// The number of ticks that a follower will wait for a message from the leader before becoming a candidate and starting an election. `ElectionTick` must be greater than `HeartbeatTick`. A tick currently defaults to one second, so these translate directly to seconds currently, but this is NOT guaranteed.
#[serde(rename = "ElectionTick")]
pub election_tick: Option<I64>,
/// The number of ticks between heartbeats.
/// Every HeartbeatTick ticks, the leader will send a heartbeat to the followers.
/// A tick currently defaults to one second, so these translate directly to seconds currently, but this is NOT guaranteed.
#[serde(rename = "HeartbeatTick")]
pub heartbeat_tick: Option<I64>,
}
/// Dispatcher configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecDispatcher {
/// The delay for an agent to send a heartbeat to the dispatcher.
#[serde(rename = "HeartbeatPeriod")]
pub heartbeat_period: Option<I64>,
}
/// CA configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecCaConfig {
/// The duration node certificates are issued for.
#[serde(rename = "NodeCertExpiry")]
pub node_cert_expiry: Option<I64>,
/// Configuration for forwarding signing requests to an external certificate authority.
#[serde(rename = "ExternalCAs")]
pub external_cas: Option<Vec<SwarmSpecCaConfigExternalCas>>,
/// The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format.
#[serde(rename = "SigningCACert")]
pub signing_ca_cert: Option<String>,
/// The desired signing CA key for all swarm node TLS leaf certificates, in PEM format.
#[serde(rename = "SigningCAKey")]
pub signing_ca_key: Option<String>,
/// An integer whose purpose is to force swarm to generate a new signing CA certificate and key, if none have been specified in `SigningCACert` and `SigningCAKey`
#[serde(rename = "ForceRotate")]
pub force_rotate: Option<U64>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecCaConfigExternalCas {
/// Protocol for communication with the external CA (currently only `cfssl` is supported).
#[serde(rename = "Protocol")]
pub protocol: Option<SwarmSpecCaConfigExternalCasProtocolEnum>,
/// URL where certificate signing requests should be sent.
#[serde(rename = "URL")]
pub url: Option<String>,
/// An object with key/value pairs that are interpreted as protocol-specific options for the external CA driver.
#[serde(rename = "Options")]
pub options: Option<HashMap<String, String>>,
/// The root CA certificate (in PEM format) this external CA uses to issue TLS certificates (assumed to be to the current swarm root CA certificate if not provided).
#[serde(rename = "CACert")]
pub ca_cert: Option<String>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum SwarmSpecCaConfigExternalCasProtocolEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "cfssl")]
CFSSL,
}
/// Parameters related to encryption-at-rest.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecEncryptionConfig {
/// If set, generate a key and use it to lock data stored on the managers.
#[serde(rename = "AutoLockManagers")]
pub auto_lock_managers: Option<bool>,
}
/// Defaults for creating tasks in this cluster.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecTaskDefaults {
#[serde(rename = "LogDriver")]
pub log_driver: Option<SwarmSpecTaskDefaultsLogDriver>,
}
/// The log driver to use for tasks created in the orchestrator if unspecified by a service. Updating this value only affects new tasks. Existing tasks continue to use their previously configured log driver until recreated.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmSpecTaskDefaultsLogDriver {
/// The log driver to use as a default for new tasks.
#[serde(rename = "Name")]
pub name: Option<String>,
/// Driver-specific options for the selected log driver, specified as key/value pairs.
#[serde(rename = "Options")]
pub options: Option<HashMap<String, String>>,
}
/// JoinTokens contains the tokens workers and managers need to join the swarm.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct JoinTokens {
/// The token workers can use to join the swarm.
#[serde(rename = "Worker")]
pub worker: Option<String>,
/// The token managers can use to join the swarm.
#[serde(rename = "Manager")]
pub manager: Option<String>,
}
+788
View File
@@ -0,0 +1,788 @@
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use super::*;
/// Swarm task list item.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmTaskListItem {
/// The ID of the task.
#[serde(rename = "ID")]
pub id: Option<String>,
/// Name of the task.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The ID of the node that this task is on.
#[serde(rename = "NodeID")]
pub node_id: Option<String>,
/// The ID of the service this task is part of.
#[serde(rename = "ServiceID")]
pub service_id: Option<String>,
/// The ID of container associated with this task.
#[serde(rename = "ContainerID")]
pub container_id: Option<String>,
#[serde(rename = "State")]
pub state: Option<TaskState>,
#[serde(rename = "DesiredState")]
pub desired_state: Option<TaskState>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
}
/// Swarm task details.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct SwarmTask {
/// The ID of the task.
#[serde(rename = "ID")]
pub id: Option<String>,
#[serde(rename = "Version")]
pub version: Option<ObjectVersion>,
#[serde(rename = "CreatedAt")]
pub created_at: Option<String>,
#[serde(rename = "UpdatedAt")]
pub updated_at: Option<String>,
/// Name of the task.
#[serde(rename = "Name")]
pub name: Option<String>,
/// User-defined key/value metadata.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
#[serde(rename = "Spec")]
pub spec: Option<TaskSpec>,
/// The ID of the service this task is part of.
#[serde(rename = "ServiceID")]
pub service_id: Option<String>,
#[serde(rename = "Slot")]
pub slot: Option<I64>,
/// The ID of the node that this task is on.
#[serde(rename = "NodeID")]
pub node_id: Option<String>,
#[serde(rename = "AssignedGenericResources")]
pub assigned_generic_resources: Option<GenericResources>,
#[serde(rename = "Status")]
pub status: Option<TaskStatus>,
#[serde(rename = "DesiredState")]
pub desired_state: Option<TaskState>,
/// If the Service this Task belongs to is a job-mode service, contains the JobIteration of the Service this Task was created for. Absent if the Task was created for a Replicated or Global Service.
#[serde(rename = "JobIteration")]
pub job_iteration: Option<ObjectVersion>,
}
/// User modifiable task configuration.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpec {
#[serde(rename = "PluginSpec")]
pub plugin_spec: Option<TaskSpecPluginSpec>,
#[serde(rename = "ContainerSpec")]
pub container_spec: Option<TaskSpecContainerSpec>,
#[serde(rename = "NetworkAttachmentSpec")]
pub network_attachment_spec: Option<TaskSpecNetworkAttachmentSpec>,
#[serde(rename = "Resources")]
pub resources: Option<TaskSpecResources>,
#[serde(rename = "RestartPolicy")]
pub restart_policy: Option<TaskSpecRestartPolicy>,
#[serde(rename = "Placement")]
pub placement: Option<TaskSpecPlacement>,
/// A counter that triggers an update even if no relevant parameters have been changed.
#[serde(rename = "ForceUpdate")]
pub force_update: Option<U64>,
/// Runtime is the type of runtime specified for the task executor.
#[serde(rename = "Runtime")]
pub runtime: Option<String>,
/// Specifies which networks the service should attach to.
#[serde(rename = "Networks")]
pub networks: Option<Vec<NetworkAttachmentConfig>>,
#[serde(rename = "LogDriver")]
pub log_driver: Option<TaskSpecLogDriver>,
}
/// Plugin spec for the service.
/// *(Experimental release only.)*
/// <p><br /></p> > **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive. PluginSpec is only used when the Runtime field > is set to `plugin`. NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecPluginSpec {
/// The name or 'alias' to use for the plugin.
#[serde(rename = "Name")]
pub name: Option<String>,
/// The plugin image reference to use.
#[serde(rename = "Remote")]
pub remote: Option<String>,
/// Disable the plugin once scheduled.
#[serde(rename = "Disabled")]
pub disabled: Option<bool>,
#[serde(rename = "PluginPrivilege")]
pub plugin_privilege: Option<Vec<PluginPrivilege>>,
}
/// Describes a permission the user has to accept upon installing the plugin.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct PluginPrivilege {
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "Description")]
pub description: Option<String>,
#[serde(rename = "Value")]
pub value: Option<Vec<String>>,
}
/// Container spec for the service.
/// **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive.
/// PluginSpec is only used when the Runtime field > is set to `plugin`.
/// NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpec {
/// The image name to use for the container
#[serde(rename = "Image")]
pub image: Option<String>,
/// User-defined key/value data.
#[serde(rename = "Labels")]
pub labels: Option<HashMap<String, String>>,
/// The command to be run in the image.
#[serde(rename = "Command")]
pub command: Option<Vec<String>>,
/// Arguments to the command.
#[serde(rename = "Args")]
pub args: Option<Vec<String>>,
/// The hostname to use for the container, as a valid [RFC 1123](https://tools.ietf.org/html/rfc1123) hostname.
#[serde(rename = "Hostname")]
pub hostname: Option<String>,
/// A list of environment variables in the form `VAR=value`.
#[serde(rename = "Env")]
pub env: Option<Vec<String>>,
/// The working directory for commands to run in.
#[serde(rename = "Dir")]
pub dir: Option<String>,
/// The user inside the container.
#[serde(rename = "User")]
pub user: Option<String>,
/// A list of additional groups that the container process will run as.
#[serde(rename = "Groups")]
pub groups: Option<Vec<String>>,
#[serde(rename = "Privileges")]
pub privileges: Option<TaskSpecContainerSpecPrivileges>,
/// Whether a pseudo-TTY should be allocated.
#[serde(rename = "TTY")]
pub tty: Option<bool>,
/// Open `stdin`
#[serde(rename = "OpenStdin")]
pub open_stdin: Option<bool>,
/// Mount the container's root filesystem as read only.
#[serde(rename = "ReadOnly")]
pub read_only: Option<bool>,
/// Specification for mounts to be added to containers created as part of the service.
#[serde(rename = "Mounts")]
pub mounts: Option<Vec<Mount>>,
/// Signal to stop the container.
#[serde(rename = "StopSignal")]
pub stop_signal: Option<String>,
/// Amount of time to wait for the container to terminate before forcefully killing it.
#[serde(rename = "StopGracePeriod")]
pub stop_grace_period: Option<I64>,
#[serde(rename = "HealthCheck")]
pub health_check: Option<HealthConfig>,
/// A list of hostname/IP mappings to add to the container's `hosts` file. The format of extra hosts is specified in the [hosts(5)](http://man7.org/linux/man-pages/man5/hosts.5.html) man page: IP_address canonical_hostname [aliases...]
#[serde(rename = "Hosts")]
pub hosts: Option<Vec<String>>,
#[serde(rename = "DNSConfig")]
pub dns_config: Option<TaskSpecContainerSpecDnsConfig>,
/// Secrets contains references to zero or more secrets that will be exposed to the service.
#[serde(rename = "Secrets")]
pub secrets: Option<Vec<TaskSpecContainerSpecSecrets>>,
/// An integer value containing the score given to the container in order to tune OOM killer preferences.
#[serde(rename = "OomScoreAdj")]
pub oom_score_adj: Option<I64>,
/// Configs contains references to zero or more configs that will be exposed to the service.
#[serde(rename = "Configs")]
pub configs: Option<Vec<TaskSpecContainerSpecConfigs>>,
/// Isolation technology of the containers running the service. (Windows only)
#[serde(rename = "Isolation")]
pub isolation: Option<TaskSpecContainerSpecIsolationEnum>,
/// Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used.
#[serde(rename = "Init")]
pub init: Option<bool>,
/// Set kernel namedspaced parameters (sysctls) in the container. The Sysctls option on services accepts the same sysctls as the are supported on containers. Note that while the same sysctls are supported, no guarantees or checks are made about their suitability for a clustered environment, and it's up to the user to determine whether a given sysctl will work properly in a Service.
#[serde(rename = "Sysctls")]
pub sysctls: Option<HashMap<String, String>>,
/// A list of kernel capabilities to add to the default set for the container.
#[serde(rename = "CapabilityAdd")]
pub capability_add: Option<Vec<String>>,
/// A list of kernel capabilities to drop from the default set for the container.
#[serde(rename = "CapabilityDrop")]
pub capability_drop: Option<Vec<String>>,
/// A list of resource limits to set in the container. For example: `{\"Name\": \"nofile\", \"Soft\": 1024, \"Hard\": 2048}`\"
#[serde(rename = "Ulimits")]
pub ulimits: Option<Vec<ResourcesUlimits>>,
}
/// Security options for the container
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecPrivileges {
#[serde(rename = "CredentialSpec")]
pub credential_spec:
Option<TaskSpecContainerSpecPrivilegesCredentialSpec>,
#[serde(rename = "SELinuxContext")]
pub se_linux_context:
Option<TaskSpecContainerSpecPrivilegesSeLinuxContext>,
#[serde(rename = "Seccomp")]
pub seccomp: Option<TaskSpecContainerSpecPrivilegesSeccomp>,
#[serde(rename = "AppArmor")]
pub app_armor: Option<TaskSpecContainerSpecPrivilegesAppArmor>,
/// Configuration of the no_new_privs bit in the container
#[serde(rename = "NoNewPrivileges")]
pub no_new_privileges: Option<bool>,
}
/// CredentialSpec for managed service account (Windows only)
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecPrivilegesCredentialSpec {
/// Load credential spec from a Swarm Config with the given ID. The specified config must also be present in the Configs field with the Runtime property set. <p><br /></p> > **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive.
#[serde(rename = "Config")]
pub config: Option<String>,
/// Load credential spec from this file. The file is read by the daemon, and must be present in the `CredentialSpecs` subdirectory in the docker data directory, which defaults to `C:\\ProgramData\\Docker\\` on Windows. For example, specifying `spec.json` loads `C:\\ProgramData\\Docker\\CredentialSpecs\\spec.json`. <p><br /></p> > **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive.
#[serde(rename = "File")]
pub file: Option<String>,
/// Load credential spec from this value in the Windows registry. The specified registry value must be located in: `HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\\Containers\\CredentialSpecs` <p><br /></p> > **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, > and `CredentialSpec.Config` are mutually exclusive.
#[serde(rename = "Registry")]
pub registry: Option<String>,
}
/// SELinux labels of the container
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecPrivilegesSeLinuxContext {
/// Disable SELinux
#[serde(rename = "Disable")]
pub disable: Option<bool>,
/// SELinux user label
#[serde(rename = "User")]
pub user: Option<String>,
/// SELinux role label
#[serde(rename = "Role")]
pub role: Option<String>,
/// SELinux type label
#[serde(rename = "Type")]
pub typ: Option<String>,
/// SELinux level label
#[serde(rename = "Level")]
pub level: Option<String>,
}
/// Options for configuring seccomp on the container
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecPrivilegesSeccomp {
#[serde(rename = "Mode")]
pub mode: Option<TaskSpecContainerSpecPrivilegesSeccompModeEnum>,
/// The custom seccomp profile as a json object
#[serde(rename = "Profile")]
pub profile: Option<String>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum TaskSpecContainerSpecPrivilegesSeccompModeEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "default")]
DEFAULT,
#[serde(rename = "unconfined")]
UNCONFINED,
#[serde(rename = "custom")]
CUSTOM,
}
/// Options for configuring AppArmor on the container
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecPrivilegesAppArmor {
#[serde(rename = "Mode")]
pub mode: Option<TaskSpecContainerSpecPrivilegesAppArmorModeEnum>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum TaskSpecContainerSpecPrivilegesAppArmorModeEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "default")]
DEFAULT,
#[serde(rename = "disabled")]
DISABLED,
}
/// Specification for DNS related configurations in resolver configuration file (`resolv.conf`).
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecDnsConfig {
/// The IP addresses of the name servers.
#[serde(rename = "Nameservers")]
pub nameservers: Option<Vec<String>>,
/// A search list for host-name lookup.
#[serde(rename = "Search")]
pub search: Option<Vec<String>>,
/// A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.).
#[serde(rename = "Options")]
pub options: Option<Vec<String>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecSecrets {
#[serde(rename = "File")]
pub file: Option<TaskSpecContainerSpecFile>,
/// SecretID represents the ID of the specific secret that we're referencing.
#[serde(rename = "SecretID")]
pub secret_id: Option<String>,
/// SecretName is the name of the secret that this references, but this is just provided for lookup/display purposes. The secret in the reference will be identified by its ID.
#[serde(rename = "SecretName")]
pub secret_name: Option<String>,
}
/// File represents a specific target that is backed by a file.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecFile {
/// Name represents the final filename in the filesystem.
#[serde(rename = "Name")]
pub name: Option<String>,
/// UID represents the file UID.
#[serde(rename = "UID")]
pub uid: Option<String>,
/// GID represents the file GID.
#[serde(rename = "GID")]
pub gid: Option<String>,
/// Mode represents the FileMode of the file.
#[serde(rename = "Mode")]
pub mode: Option<u32>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecContainerSpecConfigs {
#[serde(rename = "File")]
pub file: Option<TaskSpecContainerSpecFile>,
// /// Runtime represents a target that is not mounted into the container but is used by the task <p><br /><p> > **Note**: `Configs.File` and `Configs.Runtime` are mutually > exclusive
// #[serde(rename = "Runtime")]
// pub runtime: Option<HashMap<(), ()>>,
/// ConfigID represents the ID of the specific config that we're referencing.
#[serde(rename = "ConfigID")]
pub config_id: Option<String>,
/// ConfigName is the name of the config that this references, but this is just provided for lookup/display purposes. The config in the reference will be identified by its ID.
#[serde(rename = "ConfigName")]
pub config_name: Option<String>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum TaskSpecContainerSpecIsolationEnum {
#[serde(rename = "default")]
DEFAULT,
#[serde(rename = "process")]
PROCESS,
#[serde(rename = "hyperv")]
HYPERV,
#[default]
#[serde(rename = "")]
EMPTY,
}
/// Read-only spec type for non-swarm containers attached to swarm overlay networks. <p><br /></p> > **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are > mutually exclusive. PluginSpec is only used when the Runtime field > is set to `plugin`. NetworkAttachmentSpec is used when the Runtime > field is set to `attachment`.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecNetworkAttachmentSpec {
/// ID of the container represented by this task
#[serde(rename = "ContainerID")]
pub container_id: Option<String>,
}
/// Resource requirements which apply to each individual container created as part of the service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecResources {
/// Define resources limits.
#[serde(rename = "Limits")]
pub limits: Option<Limit>,
/// Define resources reservation.
#[serde(rename = "Reservations")]
pub reservations: Option<ResourceObject>,
}
/// An object describing a limit on resources which can be requested by a task.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct Limit {
#[serde(rename = "NanoCPUs")]
pub nano_cpus: Option<I64>,
#[serde(rename = "MemoryBytes")]
pub memory_bytes: Option<I64>,
/// Limits the maximum number of PIDs in the container. Set `0` for unlimited.
#[serde(rename = "Pids")]
pub pids: Option<I64>,
}
/// Specification for the restart policy which applies to containers created as part of this service.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecRestartPolicy {
/// Condition for restart.
#[serde(rename = "Condition")]
pub condition: Option<TaskSpecRestartPolicyConditionEnum>,
/// Delay between restart attempts.
#[serde(rename = "Delay")]
pub delay: Option<I64>,
/// Maximum attempts to restart a given container before giving up (default value is 0, which is ignored).
#[serde(rename = "MaxAttempts")]
pub max_attempts: Option<I64>,
/// Windows is the time window used to evaluate the restart policy (default value is 0, which is unbounded).
#[serde(rename = "Window")]
pub window: Option<I64>,
}
#[allow(non_camel_case_types)]
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
)]
pub enum TaskSpecRestartPolicyConditionEnum {
#[default]
#[serde(rename = "")]
EMPTY,
#[serde(rename = "none")]
NONE,
#[serde(rename = "on-failure")]
ON_FAILURE,
#[serde(rename = "any")]
ANY,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecPlacement {
/// An array of constraint expressions to limit the set of nodes where a task can be scheduled. Constraint expressions can either use a _match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find nodes that satisfy every expression (AND match). Constraints can match node or Docker Engine labels as follows: node attribute | matches | example ---------------------|--------------------------------|----------------------------------------------- `node.id` | Node ID | `node.id==2ivku8v2gvtg4` `node.hostname` | Node hostname | `node.hostname!=node-2` `node.role` | Node role (`manager`/`worker`) | `node.role==manager` `node.platform.os` | Node operating system | `node.platform.os==windows` `node.platform.arch` | Node architecture | `node.platform.arch==x86_64` `node.labels` | User-defined node labels | `node.labels.security==high` `engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-24.04` `engine.labels` apply to Docker Engine labels like operating system, drivers, etc. Swarm administrators add `node.labels` for operational purposes by using the [`node update endpoint`](#operation/NodeUpdate).
#[serde(rename = "Constraints")]
pub constraints: Option<Vec<String>>,
/// Preferences provide a way to make the scheduler aware of factors such as topology. They are provided in order from highest to lowest precedence.
#[serde(rename = "Preferences")]
pub preferences: Option<Vec<TaskSpecPlacementPreferences>>,
/// Maximum number of replicas for per node (default value is 0, which is unlimited)
#[serde(rename = "MaxReplicas")]
pub max_replicas: Option<I64>,
/// Platforms stores all the platforms that the service's image can run on. This field is used in the platform filter for scheduling. If empty, then the platform filter is off, meaning there are no scheduling restrictions.
#[serde(rename = "Platforms")]
pub platforms: Option<Vec<Platform>>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecPlacementPreferences {
#[serde(rename = "Spread")]
pub spread: Option<TaskSpecPlacementSpread>,
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecPlacementSpread {
/// label descriptor, such as `engine.labels.az`.
#[serde(rename = "SpreadDescriptor")]
pub spread_descriptor: Option<String>,
}
/// Specifies the log driver to use for tasks created from this spec.
/// If not present, the default one for the swarm will be used,
/// finally falling back to the engine default if not specified.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskSpecLogDriver {
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "Options")]
pub options: Option<HashMap<String, String>>,
}
/// represents the status of a task.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct TaskStatus {
#[serde(rename = "Timestamp")]
pub timestamp: Option<String>,
#[serde(rename = "State")]
pub state: Option<TaskState>,
#[serde(rename = "Message")]
pub message: Option<String>,
#[serde(rename = "Err")]
pub err: Option<String>,
#[serde(rename = "ContainerStatus")]
pub container_status: Option<ContainerStatus>,
#[serde(rename = "PortStatus")]
pub port_status: Option<PortStatus>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
)]
pub enum TaskState {
#[serde(rename = "new")]
NEW,
#[serde(rename = "allocated")]
ALLOCATED,
#[serde(rename = "pending")]
PENDING,
#[serde(rename = "assigned")]
ASSIGNED,
#[serde(rename = "accepted")]
ACCEPTED,
#[serde(rename = "preparing")]
PREPARING,
#[serde(rename = "ready")]
READY,
#[serde(rename = "starting")]
STARTING,
#[serde(rename = "running")]
RUNNING,
#[serde(rename = "complete")]
COMPLETE,
#[serde(rename = "shutdown")]
SHUTDOWN,
#[serde(rename = "failed")]
FAILED,
#[serde(rename = "rejected")]
REJECTED,
#[serde(rename = "remove")]
REMOVE,
#[serde(rename = "orphaned")]
ORPHANED,
}
/// represents the status of a container.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ContainerStatus {
#[serde(rename = "ContainerID")]
pub container_id: Option<String>,
#[serde(rename = "PID")]
pub pid: Option<I64>,
#[serde(rename = "ExitCode")]
pub exit_code: Option<I64>,
}
/// represents the port status of a task's host ports whose service has published host ports
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct PortStatus {
#[serde(rename = "Ports")]
pub ports: Option<Vec<EndpointPortConfig>>,
}
+2 -12
View File
@@ -3,9 +3,9 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{I64, U64};
use crate::entities::I64;
use super::PortBinding;
use super::{ObjectVersion, PortBinding};
#[typeshare]
#[derive(
@@ -189,16 +189,6 @@ pub enum ClusterVolumePublishStatusStateEnum {
PendingControllerUnpublish,
}
/// The version number of the object such as node, service, etc. This is needed to avoid conflicting writes. The client must send the version number along with the modified specification when updating these objects. This approach ensures safe concurrency and determinism in that the change on the object may not be applied if the version number has changed from the last read. In other words, if two update requests specify the same base version, only one of the requests can succeed. As a result, two separate update requests that happen at the same time will not unintentionally overwrite each other.
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ObjectVersion {
#[serde(rename = "Index")]
pub index: Option<U64>,
}
/// Cluster-specific options used to create the volume.
#[typeshare]
#[derive(
+88 -75
View File
@@ -62,6 +62,8 @@ pub mod server;
pub mod stack;
/// Subtypes for server stats reporting.
pub mod stats;
/// Subtypes of [Swarm][swarm::Swarm].
pub mod swarm;
/// Subtypes of [ResourceSync][sync::ResourceSync]
pub mod sync;
/// Subtypes of [Tag][tag::Tag].
@@ -1046,7 +1048,13 @@ pub enum Operation {
#[default]
None,
// server
// Swarm
CreateSwarm,
UpdateSwarm,
RenameSwarm,
DeleteSwarm,
// Server
CreateServer,
UpdateServer,
UpdateServerKey,
@@ -1075,7 +1083,7 @@ pub enum Operation {
PruneBuildx,
PruneSystem,
// stack
// Stack
CreateStack,
UpdateStack,
RenameStack,
@@ -1092,7 +1100,7 @@ pub enum Operation {
DestroyStack,
RunStackService,
// stack (service)
// Stack (Service)
DeployStackService,
PullStackService,
StartStackService,
@@ -1102,7 +1110,7 @@ pub enum Operation {
StopStackService,
DestroyStackService,
// deployment
// Deployment
CreateDeployment,
UpdateDeployment,
RenameDeployment,
@@ -1116,7 +1124,7 @@ pub enum Operation {
StopDeployment,
DestroyDeployment,
// build
// Build
CreateBuild,
UpdateBuild,
RenameBuild,
@@ -1125,7 +1133,7 @@ pub enum Operation {
CancelBuild,
WriteDockerfile,
// repo
// Repo
CreateRepo,
UpdateRepo,
RenameRepo,
@@ -1135,35 +1143,21 @@ pub enum Operation {
BuildRepo,
CancelRepoBuild,
// procedure
// Procedure
CreateProcedure,
UpdateProcedure,
RenameProcedure,
DeleteProcedure,
RunProcedure,
// action
// Action
CreateAction,
UpdateAction,
RenameAction,
DeleteAction,
RunAction,
// builder
CreateBuilder,
UpdateBuilder,
RenameBuilder,
DeleteBuilder,
// alerter
CreateAlerter,
UpdateAlerter,
RenameAlerter,
DeleteAlerter,
TestAlerter,
SendAlert,
// sync
// Sync
CreateResourceSync,
UpdateResourceSync,
RenameResourceSync,
@@ -1172,24 +1166,38 @@ pub enum Operation {
CommitSync,
RunSync,
// maintenance
// Builder
CreateBuilder,
UpdateBuilder,
RenameBuilder,
DeleteBuilder,
// Alerter
CreateAlerter,
UpdateAlerter,
RenameAlerter,
DeleteAlerter,
TestAlerter,
SendAlert,
// Maintenance
ClearRepoCache,
BackupCoreDatabase,
GlobalAutoUpdate,
RotateAllServerKeys,
RotateCoreKeys,
// variable
// Variable
CreateVariable,
UpdateVariableValue,
DeleteVariable,
// git provider
// Git Provider
CreateGitProviderAccount,
UpdateGitProviderAccount,
DeleteGitProviderAccount,
// docker registry
// Docker Registry
CreateDockerRegistryAccount,
UpdateDockerRegistryAccount,
DeleteDockerRegistryAccount,
@@ -1273,6 +1281,7 @@ pub enum TerminationSignal {
#[serde(tag = "type", content = "id")]
pub enum ResourceTarget {
System(String),
Swarm(String),
Server(String),
Stack(String),
Deployment(String),
@@ -1301,6 +1310,7 @@ impl ResourceTarget {
pub fn is_empty(&self) -> bool {
match self {
ResourceTarget::System(id) => id.is_empty(),
ResourceTarget::Swarm(id) => id.is_empty(),
ResourceTarget::Server(id) => id.is_empty(),
ResourceTarget::Stack(id) => id.is_empty(),
ResourceTarget::Deployment(id) => id.is_empty(),
@@ -1319,6 +1329,7 @@ impl ResourceTarget {
) -> (ResourceTargetVariant, &String) {
let id = match self {
ResourceTarget::System(id) => id,
ResourceTarget::Swarm(id) => id,
ResourceTarget::Server(id) => id,
ResourceTarget::Stack(id) => id,
ResourceTarget::Build(id) => id,
@@ -1334,9 +1345,15 @@ impl ResourceTarget {
}
}
impl From<&build::Build> for ResourceTarget {
fn from(build: &build::Build) -> Self {
Self::Build(build.id.clone())
impl From<&server::Server> for ResourceTarget {
fn from(server: &server::Server) -> Self {
Self::Server(server.id.clone())
}
}
impl From<&stack::Stack> for ResourceTarget {
fn from(stack: &stack::Stack) -> Self {
Self::Stack(stack.id.clone())
}
}
@@ -1346,9 +1363,9 @@ impl From<&deployment::Deployment> for ResourceTarget {
}
}
impl From<&server::Server> for ResourceTarget {
fn from(server: &server::Server) -> Self {
Self::Server(server.id.clone())
impl From<&build::Build> for ResourceTarget {
fn from(build: &build::Build) -> Self {
Self::Build(build.id.clone())
}
}
@@ -1358,6 +1375,24 @@ impl From<&repo::Repo> for ResourceTarget {
}
}
impl From<&procedure::Procedure> for ResourceTarget {
fn from(procedure: &procedure::Procedure) -> Self {
Self::Procedure(procedure.id.clone())
}
}
impl From<&action::Action> for ResourceTarget {
fn from(action: &action::Action) -> Self {
Self::Action(action.id.clone())
}
}
impl From<&sync::ResourceSync> for ResourceTarget {
fn from(resource_sync: &sync::ResourceSync) -> Self {
Self::ResourceSync(resource_sync.id.clone())
}
}
impl From<&builder::Builder> for ResourceTarget {
fn from(builder: &builder::Builder) -> Self {
Self::Builder(builder.id.clone())
@@ -1370,45 +1405,22 @@ impl From<&alerter::Alerter> for ResourceTarget {
}
}
impl From<&procedure::Procedure> for ResourceTarget {
fn from(procedure: &procedure::Procedure) -> Self {
Self::Procedure(procedure.id.clone())
}
}
impl From<&sync::ResourceSync> for ResourceTarget {
fn from(resource_sync: &sync::ResourceSync) -> Self {
Self::ResourceSync(resource_sync.id.clone())
}
}
impl From<&stack::Stack> for ResourceTarget {
fn from(stack: &stack::Stack) -> Self {
Self::Stack(stack.id.clone())
}
}
impl From<&action::Action> for ResourceTarget {
fn from(action: &action::Action) -> Self {
Self::Action(action.id.clone())
}
}
impl ResourceTargetVariant {
/// These need to use snake case
pub fn toml_header(&self) -> &'static str {
match self {
ResourceTargetVariant::System => "system",
ResourceTargetVariant::Build => "build",
ResourceTargetVariant::Builder => "builder",
ResourceTargetVariant::Deployment => "deployment",
ResourceTargetVariant::Swarm => "swarm",
ResourceTargetVariant::Server => "server",
ResourceTargetVariant::Repo => "repo",
ResourceTargetVariant::Alerter => "alerter",
ResourceTargetVariant::Procedure => "procedure",
ResourceTargetVariant::ResourceSync => "resource_sync",
ResourceTargetVariant::Stack => "stack",
ResourceTargetVariant::Deployment => "deployment",
ResourceTargetVariant::Build => "build",
ResourceTargetVariant::Repo => "repo",
ResourceTargetVariant::Procedure => "procedure",
ResourceTargetVariant::Action => "action",
ResourceTargetVariant::ResourceSync => "resource_sync",
ResourceTargetVariant::Builder => "builder",
ResourceTargetVariant::Alerter => "alerter",
}
}
}
@@ -1446,23 +1458,18 @@ pub fn resource_link(
) -> String {
let path = match resource_type {
ResourceTargetVariant::System => unreachable!(),
ResourceTargetVariant::Build => format!("/builds/{id}"),
ResourceTargetVariant::Builder => {
format!("/builders/{id}")
}
ResourceTargetVariant::Deployment => {
format!("/deployments/{id}")
ResourceTargetVariant::Swarm => format!("/swarms/{id}"),
ResourceTargetVariant::Server => {
format!("/servers/{id}")
}
ResourceTargetVariant::Stack => {
format!("/stacks/{id}")
}
ResourceTargetVariant::Server => {
format!("/servers/{id}")
ResourceTargetVariant::Deployment => {
format!("/deployments/{id}")
}
ResourceTargetVariant::Build => format!("/builds/{id}"),
ResourceTargetVariant::Repo => format!("/repos/{id}"),
ResourceTargetVariant::Alerter => {
format!("/alerters/{id}")
}
ResourceTargetVariant::Procedure => {
format!("/procedures/{id}")
}
@@ -1472,6 +1479,12 @@ pub fn resource_link(
ResourceTargetVariant::ResourceSync => {
format!("/resource-syncs/{id}")
}
ResourceTargetVariant::Builder => {
format!("/builders/{id}")
}
ResourceTargetVariant::Alerter => {
format!("/alerters/{id}")
}
};
format!("{host}{path}")
}
+111 -14
View File
@@ -79,14 +79,24 @@ impl Stack {
false
}
pub fn all_file_paths(&self) -> Vec<String> {
/// Get tracked additional env files (those that Komodo should manage)
fn tracked_env_files(&self) -> impl Iterator<Item = &str> {
self
.config
.additional_env_files
.iter()
.filter(|f| f.track)
.map(|f| f.path.as_str())
}
pub fn all_tracked_file_paths(&self) -> Vec<String> {
let mut res = self
.compose_file_paths()
.iter()
.cloned()
// Makes sure to dedup them, while maintaining ordering
.collect::<IndexSet<_>>();
res.extend(self.config.additional_env_files.clone());
res.extend(self.tracked_env_files().map(str::to_string));
res.extend(
self.config.config_files.iter().map(|f| f.path.clone()),
);
@@ -103,11 +113,8 @@ impl Stack {
.collect::<IndexSet<_>>();
res.extend(
self
.config
.additional_env_files
.iter()
.cloned()
.map(StackFileDependency::full_redeploy),
.tracked_env_files()
.map(|p| StackFileDependency::full_redeploy(p.to_string())),
);
res.extend(self.config.config_files.clone());
res.into_iter().collect()
@@ -450,13 +457,10 @@ pub struct StackConfig {
///
/// Note. It is already included as an `additional_file`.
/// Don't add it again there.
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[serde(default)]
#[partial_attr(serde(default))]
#[builder(default)]
pub additional_env_files: Vec<String>,
pub additional_env_files: Vec<AdditionalEnvFile>,
/// Add additional config files either in repo or on host to track.
/// Can add any files associated with the stack to enable editing them in the UI.
@@ -517,6 +521,17 @@ pub struct StackConfig {
#[builder(default)]
pub build_extra_args: Vec<String>,
/// Optional command wrapper for secrets management tools.
/// Wraps the docker compose up command with a prefix command.
/// Use [[COMPOSE_COMMAND]] as placeholder for the full compose command.
///
/// Examples:
/// - "op run -- [[COMPOSE_COMMAND]]" (1password CLI)
/// - "sops exec-file --no-fifo /path/to/secret.env '[[COMPOSE_COMMAND]]'" (sops)
#[serde(default)]
#[builder(default)]
pub compose_cmd_wrapper: String,
/// Ignore certain services declared in the compose file when checking
/// the stack status. For example, an init service might be exited, but the
/// stack should be healthy. This init service should be in `ignore_services`
@@ -619,6 +634,7 @@ impl Default for StackConfig {
run_build: Default::default(),
destroy_before_deploy: Default::default(),
build_extra_args: Default::default(),
compose_cmd_wrapper: Default::default(),
skip_secret_interp: Default::default(),
linked_repo: Default::default(),
git_provider: default_git_provider(),
@@ -639,7 +655,9 @@ impl Default for StackConfig {
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ComposeProject {
/// The compose project name.
pub name: String,
@@ -818,6 +836,85 @@ pub enum StackFileRequires {
None,
}
/// Additional env file configuration for Stack.
/// Supports backward compatibility with string-only format.
#[typeshare]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct AdditionalEnvFile {
/// File path relative to run directory
pub path: String,
/// Whether Komodo should track this file's contents.
/// If true (default), Komodo will read, display, diff, and validate.
/// If false, only passed to docker compose via --env-file.
/// Useful for externally managed files (e.g., sops decrypted files).
#[serde(default = "default_true")]
pub track: bool,
}
fn default_true() -> bool {
true
}
/// Used with custom de/serializer for [AdditionalEnvFile]
#[derive(Deserialize)]
struct __AdditionalEnvFile {
path: String,
#[serde(default = "default_true")]
track: bool,
}
impl<'de> Deserialize<'de> for AdditionalEnvFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct AdditionalEnvFileVisitor;
impl<'de> Visitor<'de> for AdditionalEnvFileVisitor {
type Value = AdditionalEnvFile;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
write!(formatter, "string or AdditionalEnvFile (object)")
}
fn visit_string<E>(self, path: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(AdditionalEnvFile {
path,
track: default_true(),
})
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::visit_string(self, v.to_string())
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
__AdditionalEnvFile::deserialize(
MapAccessDeserializer::new(map).into_deserializer(),
)
.map(|v| AdditionalEnvFile {
path: v.path,
track: v.track,
})
}
}
deserializer.deserialize_any(AdditionalEnvFileVisitor)
}
}
/// Configure additional file dependencies of the Stack.
#[typeshare]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
+109
View File
@@ -0,0 +1,109 @@
use bson::{Document, doc};
use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum::Display;
use typeshare::typeshare;
use crate::deserializers::{
option_string_list_deserializer, string_list_deserializer,
};
use super::resource::{Resource, ResourceListItem, ResourceQuery};
#[typeshare]
pub type SwarmListItem = ResourceListItem<SwarmListItemInfo>;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct SwarmListItemInfo {
/// Servers part of the swarm
pub server_ids: Vec<String>,
/// The Swarm state
pub state: SwarmState,
/// If there is an error reaching
/// Swarm, message will be given here.
pub err: Option<String>,
}
#[typeshare]
#[derive(
Debug, Clone, Copy, Default, Serialize, Deserialize, Display,
)]
pub enum SwarmState {
/// Unknown case
#[default]
Unknown,
/// The Swarm is healthy, all nodes OK
Healthy,
/// The Swarm is unhealthy
Unhealthy,
}
#[typeshare]
pub type Swarm = Resource<SwarmConfig, SwarmInfo>;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct SwarmInfo {}
#[typeshare(serialized_as = "Partial<SwarmConfig>")]
pub type _PartialSwarmConfig = PartialSwarmConfig;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)]
#[partial_derive(Serialize, Deserialize, Debug, Clone, Default)]
#[partial(skip_serializing_none, from, diff)]
pub struct SwarmConfig {
/// The Servers which are swarm manager nodes.
/// If a Server is not reachable or gives error,
/// tries the next Server.
#[serde(default, alias = "servers")]
#[partial_attr(serde(alias = "servers"))]
#[builder(default)]
pub server_ids: Vec<String>,
/// Configure quick links that are displayed in the resource header
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[builder(default)]
pub links: Vec<String>,
}
impl Default for SwarmConfig {
fn default() -> Self {
Self {
server_ids: Default::default(),
links: Default::default(),
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
pub struct SwarmActionState {}
#[typeshare]
pub type SwarmQuery = ResourceQuery<SwarmQuerySpecifics>;
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder,
)]
pub struct SwarmQuerySpecifics {
/// Filter swarms by server ids.
pub servers: Vec<String>,
}
impl super::resource::AddFilters for SwarmQuerySpecifics {
fn add_filters(&self, filters: &mut Document) {
if !self.servers.is_empty() {
filters
.insert("config.server_ids", doc! { "$in": &self.servers });
}
}
}
+9
View File
@@ -2,6 +2,8 @@ use indexmap::{IndexMap, IndexSet};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::swarm::_PartialSwarmConfig;
use super::{
ResourceTarget, ResourceTargetVariant,
action::_PartialActionConfig,
@@ -24,6 +26,13 @@ use super::{
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourcesToml {
#[serde(
default,
alias = "swarm",
skip_serializing_if = "Vec::is_empty"
)]
pub swarms: Vec<ResourceToml<_PartialSwarmConfig>>,
#[serde(
default,
alias = "server",
+120 -98
View File
@@ -22,36 +22,27 @@ export type ReadResponses = {
ListGitProvidersFromConfig: Types.ListGitProvidersFromConfigResponse;
ListDockerRegistriesFromConfig: Types.ListDockerRegistriesFromConfigResponse;
// ==== USER ====
GetUsername: Types.GetUsernameResponse;
GetPermission: Types.GetPermissionResponse;
FindUser: Types.FindUserResponse;
ListUsers: Types.ListUsersResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListApiKeysForServiceUser: Types.ListApiKeysForServiceUserResponse;
ListPermissions: Types.ListPermissionsResponse;
ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse;
// ==== USER GROUP ====
GetUserGroup: Types.GetUserGroupResponse;
ListUserGroups: Types.ListUserGroupsResponse;
// ==== PROCEDURE ====
GetProceduresSummary: Types.GetProceduresSummaryResponse;
GetProcedure: Types.GetProcedureResponse;
GetProcedureActionState: Types.GetProcedureActionStateResponse;
ListProcedures: Types.ListProceduresResponse;
ListFullProcedures: Types.ListFullProceduresResponse;
// ==== ACTION ====
GetActionsSummary: Types.GetActionsSummaryResponse;
GetAction: Types.GetActionResponse;
GetActionActionState: Types.GetActionActionStateResponse;
ListActions: Types.ListActionsResponse;
ListFullActions: Types.ListFullActionsResponse;
// ==== SCHEDULE ====
ListSchedules: Types.ListSchedulesResponse;
// ==== SWARM ====
GetSwarmsSummary: Types.GetSwarmsSummaryResponse;
GetSwarm: Types.GetSwarmResponse;
GetSwarmActionState: Types.GetSwarmActionStateResponse;
ListSwarms: Types.ListSwarmsResponse;
ListFullSwarms: Types.ListFullSwarmsResponse;
InspectSwarm: Types.InspectSwarmResponse;
ListSwarmNodes: Types.ListSwarmNodesResponse;
InspectSwarmNode: Types.InspectSwarmNodeResponse;
ListSwarmConfigs: Types.ListSwarmConfigsResponse;
InspectSwarmConfig: Types.InspectSwarmConfigResponse;
ListSwarmSecrets: Types.ListSwarmSecretsResponse;
InspectSwarmSecret: Types.InspectSwarmSecretResponse;
ListSwarmStacks: Types.ListSwarmStacksResponse;
InspectSwarmStack: Types.InspectSwarmStackResponse;
ListSwarmTasks: Types.ListSwarmTasksResponse;
InspectSwarmTask: Types.InspectSwarmTaskResponse;
ListSwarmServices: Types.ListSwarmServicesResponse;
InspectSwarmService: Types.InspectSwarmServiceResponse;
GetSwarmServiceLog: Types.GetSwarmServiceLogResponse;
SearchSwarmServiceLog: Types.SearchSwarmServiceLogResponse;
// ==== SERVER ====
GetServersSummary: Types.GetServersSummaryResponse;
@@ -59,9 +50,10 @@ export type ReadResponses = {
GetServerState: Types.GetServerStateResponse;
GetPeripheryInformation: Types.GetPeripheryInformationResponse;
GetServerActionState: Types.GetServerActionStateResponse;
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
ListServers: Types.ListServersResponse;
ListFullServers: Types.ListFullServersResponse;
// ==== TERMINAL ====
ListTerminals: Types.ListTerminalsResponse;
// ==== DOCKER ====
@@ -81,6 +73,12 @@ export type ReadResponses = {
ListDockerVolumes: Types.ListDockerVolumesResponse;
InspectDockerVolume: Types.InspectDockerVolumeResponse;
// ==== SERVER STATS ====
GetSystemInformation: Types.GetSystemInformationResponse;
GetSystemStats: Types.GetSystemStatsResponse;
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
ListSystemProcesses: Types.ListSystemProcessesResponse;
// ==== STACK ====
GetStacksSummary: Types.GetStacksSummaryResponse;
GetStack: Types.GetStackResponse;
@@ -124,6 +122,23 @@ export type ReadResponses = {
ListRepos: Types.ListReposResponse;
ListFullRepos: Types.ListFullReposResponse;
// ==== PROCEDURE ====
GetProceduresSummary: Types.GetProceduresSummaryResponse;
GetProcedure: Types.GetProcedureResponse;
GetProcedureActionState: Types.GetProcedureActionStateResponse;
ListProcedures: Types.ListProceduresResponse;
ListFullProcedures: Types.ListFullProceduresResponse;
// ==== ACTION ====
GetActionsSummary: Types.GetActionsSummaryResponse;
GetAction: Types.GetActionResponse;
GetActionActionState: Types.GetActionActionStateResponse;
ListActions: Types.ListActionsResponse;
ListFullActions: Types.ListFullActionsResponse;
// ==== SCHEDULE ====
ListSchedules: Types.ListSchedulesResponse;
// ==== SYNC ====
GetResourceSyncsSummary: Types.GetResourceSyncsSummaryResponse;
GetResourceSync: Types.GetResourceSyncResponse;
@@ -151,6 +166,20 @@ export type ReadResponses = {
GetTag: Types.GetTagResponse;
ListTags: Types.ListTagsResponse;
// ==== USER ====
GetUsername: Types.GetUsernameResponse;
GetPermission: Types.GetPermissionResponse;
FindUser: Types.FindUserResponse;
ListUsers: Types.ListUsersResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListApiKeysForServiceUser: Types.ListApiKeysForServiceUserResponse;
ListPermissions: Types.ListPermissionsResponse;
ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse;
// ==== USER GROUP ====
GetUserGroup: Types.GetUserGroupResponse;
ListUserGroups: Types.ListUserGroupsResponse;
// ==== UPDATE ====
GetUpdate: Types.GetUpdateResponse;
ListUpdates: Types.ListUpdatesResponse;
@@ -159,11 +188,6 @@ export type ReadResponses = {
ListAlerts: Types.ListAlertsResponse;
GetAlert: Types.GetAlertResponse;
// ==== SERVER STATS ====
GetSystemInformation: Types.GetSystemInformationResponse;
GetSystemStats: Types.GetSystemStatsResponse;
ListSystemProcesses: Types.ListSystemProcessesResponse;
// ==== VARIABLE ====
GetVariable: Types.GetVariableResponse;
ListVariables: Types.ListVariablesResponse;
@@ -179,36 +203,16 @@ export type ReadResponses = {
};
export type WriteResponses = {
// ==== USER ====
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
// ==== SERVICE USER ====
CreateServiceUser: Types.CreateServiceUserResponse;
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
// ==== USER GROUP ====
CreateUserGroup: Types.UserGroup;
RenameUserGroup: Types.UserGroup;
DeleteUserGroup: Types.UserGroup;
AddUserToUserGroup: Types.UserGroup;
RemoveUserFromUserGroup: Types.UserGroup;
SetUsersInUserGroup: Types.UserGroup;
SetEveryoneUserGroup: Types.UserGroup;
// ==== PERMISSIONS ====
UpdateUserAdmin: Types.UpdateUserAdminResponse;
UpdateUserBasePermissions: Types.UpdateUserBasePermissionsResponse;
UpdatePermissionOnResourceType: Types.UpdatePermissionOnResourceTypeResponse;
UpdatePermissionOnTarget: Types.UpdatePermissionOnTargetResponse;
// ==== RESOURCE ====
UpdateResourceMeta: Types.UpdateResourceMetaResponse;
// ==== SWARM ====
CreateSwarm: Types.Swarm;
CopySwarm: Types.Swarm;
DeleteSwarm: Types.Swarm;
UpdateSwarm: Types.Swarm;
RenameSwarm: Types.Update;
// ==== SERVER ====
CreateServer: Types.Server;
CopyServer: Types.Server;
@@ -219,6 +223,12 @@ export type WriteResponses = {
UpdateServerPublicKey: Types.Update;
RotateServerKeys: Types.Update;
// ==== TERMINAL ====
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
// ==== STACK ====
CreateStack: Types.Stack;
CopyStack: Types.Stack;
@@ -245,13 +255,6 @@ export type WriteResponses = {
WriteBuildFileContents: Types.Update;
RefreshBuildCache: Types.NoData;
// ==== BUILDER ====
CreateBuilder: Types.Builder;
CopyBuilder: Types.Builder;
DeleteBuilder: Types.Builder;
UpdateBuilder: Types.Builder;
RenameBuilder: Types.Update;
// ==== REPO ====
CreateRepo: Types.Repo;
CopyRepo: Types.Repo;
@@ -260,13 +263,6 @@ export type WriteResponses = {
RenameRepo: Types.Update;
RefreshRepoCache: Types.NoData;
// ==== ALERTER ====
CreateAlerter: Types.Alerter;
CopyAlerter: Types.Alerter;
DeleteAlerter: Types.Alerter;
UpdateAlerter: Types.Alerter;
RenameAlerter: Types.Update;
// ==== PROCEDURE ====
CreateProcedure: Types.Procedure;
CopyProcedure: Types.Procedure;
@@ -291,11 +287,51 @@ export type WriteResponses = {
WriteSyncFileContents: Types.Update;
RefreshResourceSyncPending: Types.ResourceSync;
// ==== TERMINAL ====
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
// ==== BUILDER ====
CreateBuilder: Types.Builder;
CopyBuilder: Types.Builder;
DeleteBuilder: Types.Builder;
UpdateBuilder: Types.Builder;
RenameBuilder: Types.Update;
// ==== ALERTER ====
CreateAlerter: Types.Alerter;
CopyAlerter: Types.Alerter;
DeleteAlerter: Types.Alerter;
UpdateAlerter: Types.Alerter;
RenameAlerter: Types.Update;
// ==== ONBOARDING KEY ====
CreateOnboardingKey: Types.CreateOnboardingKeyResponse;
UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse;
DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse;
// ==== USER ====
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
// ==== SERVICE USER ====
CreateServiceUser: Types.CreateServiceUserResponse;
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
// ==== USER GROUP ====
CreateUserGroup: Types.UserGroup;
RenameUserGroup: Types.UserGroup;
DeleteUserGroup: Types.UserGroup;
AddUserToUserGroup: Types.UserGroup;
RemoveUserFromUserGroup: Types.UserGroup;
SetUsersInUserGroup: Types.UserGroup;
SetEveryoneUserGroup: Types.UserGroup;
// ==== PERMISSIONS ====
UpdateUserAdmin: Types.UpdateUserAdminResponse;
UpdateUserBasePermissions: Types.UpdateUserBasePermissionsResponse;
UpdatePermissionOnResourceType: Types.UpdatePermissionOnResourceTypeResponse;
UpdatePermissionOnTarget: Types.UpdatePermissionOnTargetResponse;
// ==== TAG ====
CreateTag: Types.Tag;
@@ -310,7 +346,7 @@ export type WriteResponses = {
UpdateVariableIsSecret: Types.UpdateVariableIsSecretResponse;
DeleteVariable: Types.DeleteVariableResponse;
// ==== PROVIDERS ====
// ==== PROVIDER ====
CreateGitProviderAccount: Types.CreateGitProviderAccountResponse;
UpdateGitProviderAccount: Types.UpdateGitProviderAccountResponse;
DeleteGitProviderAccount: Types.DeleteGitProviderAccountResponse;
@@ -318,11 +354,6 @@ export type WriteResponses = {
UpdateDockerRegistryAccount: Types.UpdateDockerRegistryAccountResponse;
DeleteDockerRegistryAccount: Types.DeleteDockerRegistryAccountResponse;
// ==== ONBOARDING KEY ====
CreateOnboardingKey: Types.CreateOnboardingKeyResponse;
UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse;
DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse;
// ==== ALERT ====
CloseAlert: Types.NoData;
};
@@ -365,6 +396,7 @@ export type ExecuteResponses = {
UnpauseStack: Types.Update;
DestroyStack: Types.Update;
BatchDestroyStack: Types.BatchExecutionResponse;
RunStackService: Types.Update;
// ==== DEPLOYMENT ====
Deploy: Types.Update;
@@ -403,16 +435,6 @@ export type ExecuteResponses = {
// ==== SYNC ====
RunSync: Types.Update;
// ==== STACK Service ====
DeployStackService: Types.Update;
StartStackService: Types.Update;
RestartStackService: Types.Update;
StopStackService: Types.Update;
PauseStackService: Types.Update;
UnpauseStackService: Types.Update;
DestroyStackService: Types.Update;
RunStackService: Types.Update;
// ==== ALERTER ====
TestAlerter: Types.Update;
SendAlert: Types.Update;
File diff suppressed because it is too large Load Diff
+2 -34
View File
@@ -1,12 +1,5 @@
use komodo_client::entities::{
config::{DockerRegistry, GitProvider},
docker::{
container::ContainerListItem, image::ImageListItem,
network::NetworkListItem, volume::VolumeListItem,
},
server::PeripheryInformation,
stack::ComposeProject,
stats::{SystemInformation, SystemStats},
update::Log,
};
use resolver_api::Resolve;
@@ -18,7 +11,9 @@ pub mod container;
pub mod docker;
pub mod git;
pub mod keys;
pub mod poll;
pub mod stats;
pub mod swarm;
pub mod terminal;
//
@@ -59,33 +54,6 @@ pub struct GetVersionResponse {
//
/// This is the data Core uses to update all Server-related status caches.
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(PollStatusResponse)]
#[error(anyhow::Error)]
pub struct PollStatus {
/// Some servers have stats monitoring disabled.
pub include_stats: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PollStatusResponse {
pub periphery_info: PeripheryInformation,
/// Basic system information
pub system_info: SystemInformation,
/// Current System Stats (Cpu, Mem, Disk)
pub system_stats: Option<SystemStats>,
// Docker lists
pub containers: Vec<ContainerListItem>,
pub networks: Vec<NetworkListItem>,
pub images: Vec<ImageListItem>,
pub volumes: Vec<VolumeListItem>,
pub projects: Vec<ComposeProject>,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(ListGitProvidersResponse)]
#[error(anyhow::Error)]
+29
View File
@@ -0,0 +1,29 @@
use komodo_client::entities::{
docker::DockerLists,
server::PeripheryInformation,
stats::{SystemInformation, SystemStats},
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
/// This is the data Core uses to update all Server-related status caches.
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(PollStatusResponse)]
#[error(anyhow::Error)]
pub struct PollStatus {
/// Include system stats
pub include_stats: bool,
/// Include docker info
pub include_docker: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PollStatusResponse {
pub periphery_info: PeripheryInformation,
/// Basic system information
pub system_info: SystemInformation,
/// Current System Stats (Cpu, Mem, Disk)
pub system_stats: Option<SystemStats>,
/// Docker lists
pub docker: Option<DockerLists>,
}
+214
View File
@@ -0,0 +1,214 @@
use std::collections::HashMap;
use komodo_client::entities::{
SearchCombinator,
docker::{
SwarmLists,
config::SwarmConfig,
node::{NodeSpecAvailabilityEnum, NodeSpecRoleEnum, SwarmNode},
secret::SwarmSecret,
service::SwarmService,
stack::SwarmStackLists,
swarm::SwarmInspectInfo,
task::SwarmTask,
},
update::Log,
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(PollSwarmStatusResponse)]
#[error(anyhow::Error)]
pub struct PollSwarmStatus {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PollSwarmStatusResponse {
/// Inspect swarm response
pub inspect: Option<SwarmInspectInfo>,
pub lists: SwarmLists,
}
// ======
// Node
// ======
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(SwarmNode)]
#[error(anyhow::Error)]
pub struct InspectSwarmNode {
pub node: String,
}
/// `docker node rm [OPTIONS] NODE [NODE...]`
///
/// https://docs.docker.com/reference/cli/docker/node/rm/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct RmSwarmNodes {
pub nodes: Vec<String>,
pub force: bool,
}
/// `docker node update [OPTIONS] NODE`
///
/// https://docs.docker.com/reference/cli/docker/node/update/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct UpdateSwarmNode {
pub node: String,
pub availability: Option<NodeSpecAvailabilityEnum>,
/// Add metadata to a swarm node using node labels (`key=value`).
/// You can specify a node label as a key with an empty value.
pub label_add: Option<HashMap<String, Option<String>>>,
/// Remove labels by the label key.
pub label_rm: Option<Vec<String>>,
/// Update the node role (`worker`, `manager`)
pub role: Option<NodeSpecRoleEnum>,
}
// =========
// Service
// =========
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(SwarmService)]
#[error(anyhow::Error)]
pub struct InspectSwarmService {
pub service: String,
}
/// Get a swarm service's logs.
///
/// https://docs.docker.com/reference/cli/docker/service/logs/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct GetSwarmServiceLog {
/// The name or id of service.
/// Also accepts task id.
pub service: String,
/// Pass `--tail` for only recent log contents. Max of 5000
#[serde(default = "default_tail")]
pub tail: u64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
/// Enable `--no-task-ids`
#[serde(default)]
pub no_task_ids: bool,
/// Enable `--no-resolve`
#[serde(default)]
pub no_resolve: bool,
/// Enable `--details`
#[serde(default)]
pub details: bool,
}
fn default_tail() -> u64 {
50
}
//
/// Search a swarm service's logs.
///
/// https://docs.docker.com/reference/cli/docker/service/logs/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct GetSwarmServiceLogSearch {
/// The name or id of service.
/// Also accepts task id.
pub service: String,
/// The search terms.
pub terms: Vec<String>,
/// And: Only lines matching all terms
/// Or: Lines matching any one of the terms
#[serde(default)]
pub combinator: SearchCombinator,
/// Invert the search (search for everything not matching terms)
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
/// Enable `--no-task-ids`
#[serde(default)]
pub no_task_ids: bool,
/// Enable `--no-resolve`
#[serde(default)]
pub no_resolve: bool,
/// Enable `--details`
#[serde(default)]
pub details: bool,
}
/// `docker service rm SERVICE [SERVICE...]`
///
/// https://docs.docker.com/reference/cli/docker/service/rm/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct RmSwarmServices {
pub services: Vec<String>,
}
// ======
// Task
// ======
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(SwarmTask)]
#[error(anyhow::Error)]
pub struct InspectSwarmTask {
pub task: String,
}
// ========
// Secret
// ========
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(SwarmSecret)]
#[error(anyhow::Error)]
pub struct InspectSwarmSecret {
pub secret: String,
}
// ========
// Config
// ========
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Vec<SwarmConfig>)]
#[error(anyhow::Error)]
pub struct InspectSwarmConfig {
pub config: String,
}
// =======
// Stack
// =======
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(SwarmStackLists)]
#[error(anyhow::Error)]
pub struct InspectSwarmStack {
/// The swarm stack name
pub stack: String,
}
/// `docker stack rm [OPTIONS] STACK [STACK...]`
///
/// https://docs.docker.com/reference/cli/docker/stack/rm/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct RmSwarmStacks {
pub stacks: Vec<String>,
/// Do not wait for stack removal
pub detach: bool,
}
+2 -2
View File
@@ -49,9 +49,9 @@
"react-router-dom": "7.6.1",
"react-xtermjs": "1.0.10",
"sanitize-html": "2.17.0",
"shell-quote": "1.8.1",
"tailwind-merge": "2.6.0",
"tailwindcss-animate": "1.0.7",
"shell-quote": "1.8.1"
"tailwindcss-animate": "1.0.7"
},
"devDependencies": {
"@types/react": "19.1.6",
+87 -69
View File
@@ -18,33 +18,31 @@ export type ReadResponses = {
ListSecrets: Types.ListSecretsResponse;
ListGitProvidersFromConfig: Types.ListGitProvidersFromConfigResponse;
ListDockerRegistriesFromConfig: Types.ListDockerRegistriesFromConfigResponse;
GetUsername: Types.GetUsernameResponse;
GetPermission: Types.GetPermissionResponse;
FindUser: Types.FindUserResponse;
ListUsers: Types.ListUsersResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListApiKeysForServiceUser: Types.ListApiKeysForServiceUserResponse;
ListPermissions: Types.ListPermissionsResponse;
ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse;
GetUserGroup: Types.GetUserGroupResponse;
ListUserGroups: Types.ListUserGroupsResponse;
GetProceduresSummary: Types.GetProceduresSummaryResponse;
GetProcedure: Types.GetProcedureResponse;
GetProcedureActionState: Types.GetProcedureActionStateResponse;
ListProcedures: Types.ListProceduresResponse;
ListFullProcedures: Types.ListFullProceduresResponse;
GetActionsSummary: Types.GetActionsSummaryResponse;
GetAction: Types.GetActionResponse;
GetActionActionState: Types.GetActionActionStateResponse;
ListActions: Types.ListActionsResponse;
ListFullActions: Types.ListFullActionsResponse;
ListSchedules: Types.ListSchedulesResponse;
GetSwarmsSummary: Types.GetSwarmsSummaryResponse;
GetSwarm: Types.GetSwarmResponse;
GetSwarmActionState: Types.GetSwarmActionStateResponse;
ListSwarms: Types.ListSwarmsResponse;
ListFullSwarms: Types.ListFullSwarmsResponse;
InspectSwarm: Types.InspectSwarmResponse;
ListSwarmNodes: Types.ListSwarmNodesResponse;
InspectSwarmNode: Types.InspectSwarmNodeResponse;
ListSwarmConfigs: Types.ListSwarmConfigsResponse;
InspectSwarmConfig: Types.InspectSwarmConfigResponse;
ListSwarmSecrets: Types.ListSwarmSecretsResponse;
InspectSwarmSecret: Types.InspectSwarmSecretResponse;
ListSwarmStacks: Types.ListSwarmStacksResponse;
InspectSwarmStack: Types.InspectSwarmStackResponse;
ListSwarmTasks: Types.ListSwarmTasksResponse;
InspectSwarmTask: Types.InspectSwarmTaskResponse;
ListSwarmServices: Types.ListSwarmServicesResponse;
InspectSwarmService: Types.InspectSwarmServiceResponse;
GetSwarmServiceLog: Types.GetSwarmServiceLogResponse;
SearchSwarmServiceLog: Types.SearchSwarmServiceLogResponse;
GetServersSummary: Types.GetServersSummaryResponse;
GetServer: Types.GetServerResponse;
GetServerState: Types.GetServerStateResponse;
GetPeripheryInformation: Types.GetPeripheryInformationResponse;
GetServerActionState: Types.GetServerActionStateResponse;
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
ListServers: Types.ListServersResponse;
ListFullServers: Types.ListFullServersResponse;
ListTerminals: Types.ListTerminalsResponse;
@@ -63,6 +61,10 @@ export type ReadResponses = {
ListDockerImageHistory: Types.ListDockerImageHistoryResponse;
ListDockerVolumes: Types.ListDockerVolumesResponse;
InspectDockerVolume: Types.InspectDockerVolumeResponse;
GetSystemInformation: Types.GetSystemInformationResponse;
GetSystemStats: Types.GetSystemStatsResponse;
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
ListSystemProcesses: Types.ListSystemProcessesResponse;
GetStacksSummary: Types.GetStacksSummaryResponse;
GetStack: Types.GetStackResponse;
GetStackActionState: Types.GetStackActionStateResponse;
@@ -98,6 +100,17 @@ export type ReadResponses = {
GetRepoActionState: Types.GetRepoActionStateResponse;
ListRepos: Types.ListReposResponse;
ListFullRepos: Types.ListFullReposResponse;
GetProceduresSummary: Types.GetProceduresSummaryResponse;
GetProcedure: Types.GetProcedureResponse;
GetProcedureActionState: Types.GetProcedureActionStateResponse;
ListProcedures: Types.ListProceduresResponse;
ListFullProcedures: Types.ListFullProceduresResponse;
GetActionsSummary: Types.GetActionsSummaryResponse;
GetAction: Types.GetActionResponse;
GetActionActionState: Types.GetActionActionStateResponse;
ListActions: Types.ListActionsResponse;
ListFullActions: Types.ListFullActionsResponse;
ListSchedules: Types.ListSchedulesResponse;
GetResourceSyncsSummary: Types.GetResourceSyncsSummaryResponse;
GetResourceSync: Types.GetResourceSyncResponse;
GetResourceSyncActionState: Types.GetResourceSyncActionStateResponse;
@@ -115,13 +128,20 @@ export type ReadResponses = {
ExportResourcesToToml: Types.ExportResourcesToTomlResponse;
GetTag: Types.GetTagResponse;
ListTags: Types.ListTagsResponse;
GetUsername: Types.GetUsernameResponse;
GetPermission: Types.GetPermissionResponse;
FindUser: Types.FindUserResponse;
ListUsers: Types.ListUsersResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListApiKeysForServiceUser: Types.ListApiKeysForServiceUserResponse;
ListPermissions: Types.ListPermissionsResponse;
ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse;
GetUserGroup: Types.GetUserGroupResponse;
ListUserGroups: Types.ListUserGroupsResponse;
GetUpdate: Types.GetUpdateResponse;
ListUpdates: Types.ListUpdatesResponse;
ListAlerts: Types.ListAlertsResponse;
GetAlert: Types.GetAlertResponse;
GetSystemInformation: Types.GetSystemInformationResponse;
GetSystemStats: Types.GetSystemStatsResponse;
ListSystemProcesses: Types.ListSystemProcessesResponse;
GetVariable: Types.GetVariableResponse;
ListVariables: Types.ListVariablesResponse;
GetGitProviderAccount: Types.GetGitProviderAccountResponse;
@@ -131,26 +151,12 @@ export type ReadResponses = {
ListOnboardingKeys: Types.ListOnboardingKeysResponse;
};
export type WriteResponses = {
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
CreateServiceUser: Types.CreateServiceUserResponse;
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
CreateUserGroup: Types.UserGroup;
RenameUserGroup: Types.UserGroup;
DeleteUserGroup: Types.UserGroup;
AddUserToUserGroup: Types.UserGroup;
RemoveUserFromUserGroup: Types.UserGroup;
SetUsersInUserGroup: Types.UserGroup;
SetEveryoneUserGroup: Types.UserGroup;
UpdateUserAdmin: Types.UpdateUserAdminResponse;
UpdateUserBasePermissions: Types.UpdateUserBasePermissionsResponse;
UpdatePermissionOnResourceType: Types.UpdatePermissionOnResourceTypeResponse;
UpdatePermissionOnTarget: Types.UpdatePermissionOnTargetResponse;
UpdateResourceMeta: Types.UpdateResourceMetaResponse;
CreateSwarm: Types.Swarm;
CopySwarm: Types.Swarm;
DeleteSwarm: Types.Swarm;
UpdateSwarm: Types.Swarm;
RenameSwarm: Types.Update;
CreateServer: Types.Server;
CopyServer: Types.Server;
DeleteServer: Types.Server;
@@ -159,6 +165,10 @@ export type WriteResponses = {
CreateNetwork: Types.Update;
UpdateServerPublicKey: Types.Update;
RotateServerKeys: Types.Update;
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
CreateStack: Types.Stack;
CopyStack: Types.Stack;
DeleteStack: Types.Stack;
@@ -179,22 +189,12 @@ export type WriteResponses = {
RenameBuild: Types.Update;
WriteBuildFileContents: Types.Update;
RefreshBuildCache: Types.NoData;
CreateBuilder: Types.Builder;
CopyBuilder: Types.Builder;
DeleteBuilder: Types.Builder;
UpdateBuilder: Types.Builder;
RenameBuilder: Types.Update;
CreateRepo: Types.Repo;
CopyRepo: Types.Repo;
DeleteRepo: Types.Repo;
UpdateRepo: Types.Repo;
RenameRepo: Types.Update;
RefreshRepoCache: Types.NoData;
CreateAlerter: Types.Alerter;
CopyAlerter: Types.Alerter;
DeleteAlerter: Types.Alerter;
UpdateAlerter: Types.Alerter;
RenameAlerter: Types.Update;
CreateProcedure: Types.Procedure;
CopyProcedure: Types.Procedure;
DeleteProcedure: Types.Procedure;
@@ -213,10 +213,38 @@ export type WriteResponses = {
CommitSync: Types.Update;
WriteSyncFileContents: Types.Update;
RefreshResourceSyncPending: Types.ResourceSync;
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
CreateBuilder: Types.Builder;
CopyBuilder: Types.Builder;
DeleteBuilder: Types.Builder;
UpdateBuilder: Types.Builder;
RenameBuilder: Types.Update;
CreateAlerter: Types.Alerter;
CopyAlerter: Types.Alerter;
DeleteAlerter: Types.Alerter;
UpdateAlerter: Types.Alerter;
RenameAlerter: Types.Update;
CreateOnboardingKey: Types.CreateOnboardingKeyResponse;
UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse;
DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse;
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
CreateServiceUser: Types.CreateServiceUserResponse;
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
CreateUserGroup: Types.UserGroup;
RenameUserGroup: Types.UserGroup;
DeleteUserGroup: Types.UserGroup;
AddUserToUserGroup: Types.UserGroup;
RemoveUserFromUserGroup: Types.UserGroup;
SetUsersInUserGroup: Types.UserGroup;
SetEveryoneUserGroup: Types.UserGroup;
UpdateUserAdmin: Types.UpdateUserAdminResponse;
UpdateUserBasePermissions: Types.UpdateUserBasePermissionsResponse;
UpdatePermissionOnResourceType: Types.UpdatePermissionOnResourceTypeResponse;
UpdatePermissionOnTarget: Types.UpdatePermissionOnTargetResponse;
CreateTag: Types.Tag;
DeleteTag: Types.Tag;
RenameTag: Types.Tag;
@@ -232,9 +260,6 @@ export type WriteResponses = {
CreateDockerRegistryAccount: Types.CreateDockerRegistryAccountResponse;
UpdateDockerRegistryAccount: Types.UpdateDockerRegistryAccountResponse;
DeleteDockerRegistryAccount: Types.DeleteDockerRegistryAccountResponse;
CreateOnboardingKey: Types.CreateOnboardingKeyResponse;
UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse;
DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse;
CloseAlert: Types.NoData;
};
export type ExecuteResponses = {
@@ -272,6 +297,7 @@ export type ExecuteResponses = {
UnpauseStack: Types.Update;
DestroyStack: Types.Update;
BatchDestroyStack: Types.BatchExecutionResponse;
RunStackService: Types.Update;
Deploy: Types.Update;
BatchDeploy: Types.BatchExecutionResponse;
PullDeployment: Types.Update;
@@ -297,14 +323,6 @@ export type ExecuteResponses = {
RunAction: Types.Update;
BatchRunAction: Types.BatchExecutionResponse;
RunSync: Types.Update;
DeployStackService: Types.Update;
StartStackService: Types.Update;
RestartStackService: Types.Update;
StopStackService: Types.Update;
PauseStackService: Types.Update;
UnpauseStackService: Types.Update;
DestroyStackService: Types.Update;
RunStackService: Types.Update;
TestAlerter: Types.Update;
SendAlert: Types.Update;
ClearRepoCache: Types.Update;
+1545 -171
View File
File diff suppressed because it is too large Load Diff
+152 -7
View File
@@ -65,6 +65,10 @@ export var MaintenanceScheduleType;
export var Operation;
(function (Operation) {
Operation["None"] = "None";
Operation["CreateSwarm"] = "CreateSwarm";
Operation["UpdateSwarm"] = "UpdateSwarm";
Operation["RenameSwarm"] = "RenameSwarm";
Operation["DeleteSwarm"] = "DeleteSwarm";
Operation["CreateServer"] = "CreateServer";
Operation["UpdateServer"] = "UpdateServer";
Operation["UpdateServerKey"] = "UpdateServerKey";
@@ -152,6 +156,13 @@ export var Operation;
Operation["RenameAction"] = "RenameAction";
Operation["DeleteAction"] = "DeleteAction";
Operation["RunAction"] = "RunAction";
Operation["CreateResourceSync"] = "CreateResourceSync";
Operation["UpdateResourceSync"] = "UpdateResourceSync";
Operation["RenameResourceSync"] = "RenameResourceSync";
Operation["DeleteResourceSync"] = "DeleteResourceSync";
Operation["WriteSyncContents"] = "WriteSyncContents";
Operation["CommitSync"] = "CommitSync";
Operation["RunSync"] = "RunSync";
Operation["CreateBuilder"] = "CreateBuilder";
Operation["UpdateBuilder"] = "UpdateBuilder";
Operation["RenameBuilder"] = "RenameBuilder";
@@ -162,13 +173,6 @@ export var Operation;
Operation["DeleteAlerter"] = "DeleteAlerter";
Operation["TestAlerter"] = "TestAlerter";
Operation["SendAlert"] = "SendAlert";
Operation["CreateResourceSync"] = "CreateResourceSync";
Operation["UpdateResourceSync"] = "UpdateResourceSync";
Operation["RenameResourceSync"] = "RenameResourceSync";
Operation["DeleteResourceSync"] = "DeleteResourceSync";
Operation["WriteSyncContents"] = "WriteSyncContents";
Operation["CommitSync"] = "CommitSync";
Operation["RunSync"] = "RunSync";
Operation["ClearRepoCache"] = "ClearRepoCache";
Operation["BackupCoreDatabase"] = "BackupCoreDatabase";
Operation["GlobalAutoUpdate"] = "GlobalAutoUpdate";
@@ -488,6 +492,138 @@ export var ClusterVolumePublishStatusStateEnum;
ClusterVolumePublishStatusStateEnum["PendingNodeUnpublish"] = "pending-node-unpublish";
ClusterVolumePublishStatusStateEnum["PendingControllerUnpublish"] = "pending-controller-unpublish";
})(ClusterVolumePublishStatusStateEnum || (ClusterVolumePublishStatusStateEnum = {}));
export var NodeSpecRoleEnum;
(function (NodeSpecRoleEnum) {
NodeSpecRoleEnum["EMPTY"] = "";
NodeSpecRoleEnum["WORKER"] = "worker";
NodeSpecRoleEnum["MANAGER"] = "manager";
})(NodeSpecRoleEnum || (NodeSpecRoleEnum = {}));
export var NodeSpecAvailabilityEnum;
(function (NodeSpecAvailabilityEnum) {
NodeSpecAvailabilityEnum["EMPTY"] = "";
NodeSpecAvailabilityEnum["ACTIVE"] = "active";
NodeSpecAvailabilityEnum["PAUSE"] = "pause";
NodeSpecAvailabilityEnum["DRAIN"] = "drain";
})(NodeSpecAvailabilityEnum || (NodeSpecAvailabilityEnum = {}));
/** NodeState represents the state of a node. */
export var NodeState;
(function (NodeState) {
NodeState["UNKNOWN"] = "unknown";
NodeState["DOWN"] = "down";
NodeState["READY"] = "ready";
NodeState["DISCONNECTED"] = "disconnected";
})(NodeState || (NodeState = {}));
/** Reachability represents the reachability of a node. */
export var NodeReachability;
(function (NodeReachability) {
NodeReachability["UNKNOWN"] = "unknown";
NodeReachability["UNREACHABLE"] = "unreachable";
NodeReachability["REACHABLE"] = "reachable";
})(NodeReachability || (NodeReachability = {}));
export var SwarmSpecCaConfigExternalCasProtocolEnum;
(function (SwarmSpecCaConfigExternalCasProtocolEnum) {
SwarmSpecCaConfigExternalCasProtocolEnum["EMPTY"] = "";
SwarmSpecCaConfigExternalCasProtocolEnum["CFSSL"] = "cfssl";
})(SwarmSpecCaConfigExternalCasProtocolEnum || (SwarmSpecCaConfigExternalCasProtocolEnum = {}));
export var TaskSpecContainerSpecPrivilegesSeccompModeEnum;
(function (TaskSpecContainerSpecPrivilegesSeccompModeEnum) {
TaskSpecContainerSpecPrivilegesSeccompModeEnum["EMPTY"] = "";
TaskSpecContainerSpecPrivilegesSeccompModeEnum["DEFAULT"] = "default";
TaskSpecContainerSpecPrivilegesSeccompModeEnum["UNCONFINED"] = "unconfined";
TaskSpecContainerSpecPrivilegesSeccompModeEnum["CUSTOM"] = "custom";
})(TaskSpecContainerSpecPrivilegesSeccompModeEnum || (TaskSpecContainerSpecPrivilegesSeccompModeEnum = {}));
export var TaskSpecContainerSpecPrivilegesAppArmorModeEnum;
(function (TaskSpecContainerSpecPrivilegesAppArmorModeEnum) {
TaskSpecContainerSpecPrivilegesAppArmorModeEnum["EMPTY"] = "";
TaskSpecContainerSpecPrivilegesAppArmorModeEnum["DEFAULT"] = "default";
TaskSpecContainerSpecPrivilegesAppArmorModeEnum["DISABLED"] = "disabled";
})(TaskSpecContainerSpecPrivilegesAppArmorModeEnum || (TaskSpecContainerSpecPrivilegesAppArmorModeEnum = {}));
export var TaskSpecContainerSpecIsolationEnum;
(function (TaskSpecContainerSpecIsolationEnum) {
TaskSpecContainerSpecIsolationEnum["DEFAULT"] = "default";
TaskSpecContainerSpecIsolationEnum["PROCESS"] = "process";
TaskSpecContainerSpecIsolationEnum["HYPERV"] = "hyperv";
TaskSpecContainerSpecIsolationEnum["EMPTY"] = "";
})(TaskSpecContainerSpecIsolationEnum || (TaskSpecContainerSpecIsolationEnum = {}));
export var TaskSpecRestartPolicyConditionEnum;
(function (TaskSpecRestartPolicyConditionEnum) {
TaskSpecRestartPolicyConditionEnum["EMPTY"] = "";
TaskSpecRestartPolicyConditionEnum["NONE"] = "none";
TaskSpecRestartPolicyConditionEnum["ON_FAILURE"] = "on-failure";
TaskSpecRestartPolicyConditionEnum["ANY"] = "any";
})(TaskSpecRestartPolicyConditionEnum || (TaskSpecRestartPolicyConditionEnum = {}));
export var ServiceSpecUpdateConfigFailureActionEnum;
(function (ServiceSpecUpdateConfigFailureActionEnum) {
ServiceSpecUpdateConfigFailureActionEnum["EMPTY"] = "";
ServiceSpecUpdateConfigFailureActionEnum["CONTINUE"] = "continue";
ServiceSpecUpdateConfigFailureActionEnum["PAUSE"] = "pause";
ServiceSpecUpdateConfigFailureActionEnum["ROLLBACK"] = "rollback";
})(ServiceSpecUpdateConfigFailureActionEnum || (ServiceSpecUpdateConfigFailureActionEnum = {}));
export var ServiceSpecUpdateConfigOrderEnum;
(function (ServiceSpecUpdateConfigOrderEnum) {
ServiceSpecUpdateConfigOrderEnum["EMPTY"] = "";
ServiceSpecUpdateConfigOrderEnum["STOP_FIRST"] = "stop-first";
ServiceSpecUpdateConfigOrderEnum["START_FIRST"] = "start-first";
})(ServiceSpecUpdateConfigOrderEnum || (ServiceSpecUpdateConfigOrderEnum = {}));
export var ServiceSpecRollbackConfigFailureActionEnum;
(function (ServiceSpecRollbackConfigFailureActionEnum) {
ServiceSpecRollbackConfigFailureActionEnum["EMPTY"] = "";
ServiceSpecRollbackConfigFailureActionEnum["CONTINUE"] = "continue";
ServiceSpecRollbackConfigFailureActionEnum["PAUSE"] = "pause";
})(ServiceSpecRollbackConfigFailureActionEnum || (ServiceSpecRollbackConfigFailureActionEnum = {}));
export var ServiceSpecRollbackConfigOrderEnum;
(function (ServiceSpecRollbackConfigOrderEnum) {
ServiceSpecRollbackConfigOrderEnum["EMPTY"] = "";
ServiceSpecRollbackConfigOrderEnum["STOP_FIRST"] = "stop-first";
ServiceSpecRollbackConfigOrderEnum["START_FIRST"] = "start-first";
})(ServiceSpecRollbackConfigOrderEnum || (ServiceSpecRollbackConfigOrderEnum = {}));
export var EndpointSpecModeEnum;
(function (EndpointSpecModeEnum) {
EndpointSpecModeEnum["EMPTY"] = "";
EndpointSpecModeEnum["VIP"] = "vip";
EndpointSpecModeEnum["DNSRR"] = "dnsrr";
})(EndpointSpecModeEnum || (EndpointSpecModeEnum = {}));
export var EndpointPortConfigProtocolEnum;
(function (EndpointPortConfigProtocolEnum) {
EndpointPortConfigProtocolEnum["EMPTY"] = "";
EndpointPortConfigProtocolEnum["TCP"] = "tcp";
EndpointPortConfigProtocolEnum["UDP"] = "udp";
EndpointPortConfigProtocolEnum["SCTP"] = "sctp";
})(EndpointPortConfigProtocolEnum || (EndpointPortConfigProtocolEnum = {}));
export var EndpointPortConfigPublishModeEnum;
(function (EndpointPortConfigPublishModeEnum) {
EndpointPortConfigPublishModeEnum["EMPTY"] = "";
EndpointPortConfigPublishModeEnum["INGRESS"] = "ingress";
EndpointPortConfigPublishModeEnum["HOST"] = "host";
})(EndpointPortConfigPublishModeEnum || (EndpointPortConfigPublishModeEnum = {}));
export var ServiceUpdateStatusStateEnum;
(function (ServiceUpdateStatusStateEnum) {
ServiceUpdateStatusStateEnum["EMPTY"] = "";
ServiceUpdateStatusStateEnum["UPDATING"] = "updating";
ServiceUpdateStatusStateEnum["PAUSED"] = "paused";
ServiceUpdateStatusStateEnum["COMPLETED"] = "completed";
ServiceUpdateStatusStateEnum["ROLLBACK_STARTED"] = "rollback_started";
ServiceUpdateStatusStateEnum["ROLLBACK_PAUSED"] = "rollback_paused";
ServiceUpdateStatusStateEnum["ROLLBACK_COMPLETED"] = "rollback_completed";
})(ServiceUpdateStatusStateEnum || (ServiceUpdateStatusStateEnum = {}));
export var TaskState;
(function (TaskState) {
TaskState["NEW"] = "new";
TaskState["ALLOCATED"] = "allocated";
TaskState["PENDING"] = "pending";
TaskState["ASSIGNED"] = "assigned";
TaskState["ACCEPTED"] = "accepted";
TaskState["PREPARING"] = "preparing";
TaskState["READY"] = "ready";
TaskState["STARTING"] = "starting";
TaskState["RUNNING"] = "running";
TaskState["COMPLETE"] = "complete";
TaskState["SHUTDOWN"] = "shutdown";
TaskState["FAILED"] = "failed";
TaskState["REJECTED"] = "rejected";
TaskState["REMOVE"] = "remove";
TaskState["ORPHANED"] = "orphaned";
})(TaskState || (TaskState = {}));
export var PortTypeEnum;
(function (PortTypeEnum) {
PortTypeEnum["EMPTY"] = "";
@@ -568,6 +704,15 @@ export var StackState;
/** Server not reachable for status */
StackState["Unknown"] = "unknown";
})(StackState || (StackState = {}));
export var SwarmState;
(function (SwarmState) {
/** Unknown case */
SwarmState["Unknown"] = "Unknown";
/** The Swarm is healthy, all nodes OK */
SwarmState["Healthy"] = "Healthy";
/** The Swarm is unhealthy */
SwarmState["Unhealthy"] = "Unhealthy";
})(SwarmState || (SwarmState = {}));
/**
* Configures the behavior of [CreateTerminal] if the
* specified terminal name already exists.
+79 -20
View File
@@ -18,7 +18,12 @@ import {
import { Box, CalendarDays, Home, Search, Terminal, User } from "lucide-react";
import { Fragment, ReactNode, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { cn, RESOURCE_TARGETS, usableResourcePath } from "@lib/utils";
import {
cn,
RESOURCE_TARGETS,
terminalLink,
usableResourcePath,
} from "@lib/utils";
import { Badge } from "@ui/badge";
import { ResourceComponents } from "./resources";
import { Switch } from "@ui/switch";
@@ -121,6 +126,8 @@ export const OmniDialog = ({
{showContainers && (
<OmniContainers search={search} closeSearch={() => setOpen(false)} />
)}
<OmniTerminals search={search} closeSearch={() => setOpen(false)} />
</CommandList>
</CommandDialog>
);
@@ -265,25 +272,77 @@ const OmniContainers = ({
<>
<CommandSeparator />
<CommandGroup heading="Containers">
{containers?.map((container) => (
<CommandItem
key={container.id}
value={container.name}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(
`/servers/${container.server_id!}/container/${container.name}`
);
}}
>
<DOCKER_LINK_ICONS.container
server_id={container.server_id!}
name={container.name}
/>
{container.name}
</CommandItem>
))}
{containers?.map((container) => {
const key = container.server_id + container.name;
return (
<CommandItem
key={key}
value={key}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(
`/servers/${container.server_id!}/container/${container.name}`
);
}}
>
<DOCKER_LINK_ICONS.container
server_id={container.server_id!}
name={container.name}
/>
{container.name}
</CommandItem>
);
})}
</CommandGroup>
</>
);
};
const OmniTerminals = ({
search,
closeSearch,
}: {
search: string;
closeSearch: () => void;
}) => {
const _terminals = useRead("ListTerminals", {}).data;
const terminals = useMemo(() => {
return _terminals?.filter((c) => {
const searchTerms = search
.toLowerCase()
.split(" ")
.filter((term) => term);
if (searchTerms.length === 0) return true;
const lower = c.name.toLowerCase();
return searchTerms.every(
(term) => lower.includes(term) || "terminals".includes(term)
);
});
}, [_terminals, search]);
const navigate = useNavigate();
if ((terminals?.length ?? 0) < 1) return null;
return (
<>
<CommandSeparator />
<CommandGroup heading="Terminals">
{terminals?.map((terminal) => {
const key = JSON.stringify(terminal.target) + terminal.name;
return (
<CommandItem
key={key}
value={key}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(terminalLink(terminal));
}}
>
<Terminal className="w-4 h-4" />
{terminal.name}
</CommandItem>
);
})}
</CommandGroup>
</>
);
@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
@@ -17,7 +17,7 @@ import {
} from "@lib/color";
import { cn } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { Card } from "@ui/card";
import { Badge } from "@ui/badge";
@@ -46,7 +46,7 @@ export const BuildComponents: RequiredResourceComponents = {
list_item: (id) => useBuild(id),
resource_links: (resource) => (resource.config as Types.BuildConfig).links,
Description: () => <>Build docker images.</>,
Description: () => <>Build container images.</>,
Dashboard: () => {
const summary = useRead("GetBuildsSummary", {}).data;
@@ -104,13 +104,6 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
description:
"Whether to connect to the instance over the public IP. Otherwise, will use the internal IP.",
},
port: {
description: "Configure the port to connect to Periphery on.",
placeholder: "Input port",
},
use_https: {
description: "Whether to connect to Periphery using HTTPS.",
},
},
},
{
@@ -132,17 +125,24 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
],
additional: [
{
label: "Auth",
label: "Connection",
labelHidden: true,
components: {
periphery_public_key: {
label: "Periphery Public Key",
description:
"If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, either this or using 'periphery_public_key' in Core config is required for Periphery to be able to connect.",
"If provided, the associated private key must be set as Periphery 'private_key'.",
placeholder: "custom-public-key",
},
port: {
description: "Configure the port to connect to Periphery on.",
placeholder: "Input port",
},
use_https: {
description: "Whether to connect to Periphery using HTTPS.",
},
insecure_tls: {
description: "Skip Periphery TLS certificate validation.",
description: "Skip Periphery TLS certificate validation when HTTPS is enabled.",
},
},
},
@@ -352,7 +352,7 @@ const UrlBuilderConfig = ({ id }: { id: string }) => {
},
},
{
label: "Auth",
label: "Connection",
labelHidden: true,
components: {
periphery_public_key: {
+7 -1
View File
@@ -299,6 +299,7 @@ export const ResourceSelector = ({
placeholder,
targetClassName,
state,
exclude_ids,
}: {
type: UsableResource;
selected: string | undefined;
@@ -309,6 +310,7 @@ export const ResourceSelector = ({
placeholder?: string;
targetClassName?: string;
state?: unknown;
exclude_ids?: string[];
}) => {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
@@ -321,7 +323,11 @@ export const ResourceSelector = ({
: () => true;
const resources = useRead(`List${type}s`, {})
.data?.filter(templateFilterFn)
.filter((r) => !state || (r.info as any).state === state);
.filter(
(r) =>
(!state || (r.info as any).state === state) &&
(!exclude_ids || r.id === selected || !exclude_ids?.includes(r.id))
);
const name = resources?.find((r) => r.id === selected)?.name;
if (!resources) return null;
@@ -24,7 +24,7 @@ import {
ResourcePageHeader,
} from "../common";
import { RunBuild } from "../build/actions";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import {
ContainerPortsTableView,
DockerResourceLink,
@@ -3,7 +3,10 @@ import { useDeployment } from ".";
import { useLocalStorage, usePermissions } from "@lib/hooks";
import { useServer } from "../server";
import { useMemo } from "react";
import { MobileFriendlyTabsSelector } from "@ui/mobile-friendly-tabs";
import {
MobileFriendlyTabsSelector,
TabNoContent,
} from "@ui/mobile-friendly-tabs";
import { DeploymentConfig } from "./config";
import { DeploymentLogs } from "./log";
import { DeploymentInspect } from "./inspect";
@@ -32,18 +35,14 @@ const DeploymentTabsInner = ({
});
const container_terminals_disabled =
useServer(deployment.info.server_id)?.info.container_terminals_disabled ??
true;
false;
const state = deployment.info.state;
const logsDisabled =
!specificLogs ||
state === undefined ||
state === Types.DeploymentState.Unknown ||
state === Types.DeploymentState.NotDeployed;
const inspectDisabled =
!specificInspect ||
const downOrUnknown =
state === undefined ||
state === Types.DeploymentState.Unknown ||
state === Types.DeploymentState.NotDeployed;
const logsDisabled = !specificLogs || downOrUnknown;
const inspectDisabled = !specificInspect || downOrUnknown;
const terminalDisabled =
!specificTerminal ||
container_terminals_disabled ||
@@ -55,34 +54,36 @@ const DeploymentTabsInner = ({
? "Config"
: _view;
const Selector = useMemo(
() => (
<MobileFriendlyTabsSelector
tabs={[
{
value: "Config",
},
{
value: "Log",
disabled: logsDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
]}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
),
const tabs = useMemo<TabNoContent<DeploymentTabsView>[]>(
() => [
{
value: "Config",
},
{
value: "Log",
disabled: logsDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
],
[logsDisabled, inspectDisabled, terminalDisabled]
);
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
);
const target: Types.TerminalTarget = useMemo(
() => ({
type: "Deployment",
+9 -7
View File
@@ -1,18 +1,20 @@
import { RequiredResourceComponents, UsableResource } from "@types";
import { AlerterComponents } from "./alerter";
import { BuildComponents } from "./build";
import { BuilderComponents } from "./builder";
import { DeploymentComponents } from "./deployment";
import { RepoComponents } from "./repo";
import { SwarmComponents } from "./swarm";
import { ServerComponents } from "./server";
import { ProcedureComponents } from "./procedure/index";
import { ResourceSyncComponents } from "./sync";
import { StackComponents } from "./stack";
import { DeploymentComponents } from "./deployment";
import { BuildComponents } from "./build";
import { RepoComponents } from "./repo";
import { ProcedureComponents } from "./procedure/index";
import { ActionComponents } from "./action";
import { BuilderComponents } from "./builder";
import { AlerterComponents } from "./alerter";
import { ResourceSyncComponents } from "./sync";
export const ResourceComponents: {
[key in UsableResource]: RequiredResourceComponents;
} = {
Swarm: SwarmComponents,
Server: ServerComponents,
Stack: StackComponents,
Deployment: DeploymentComponents,
@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
@@ -18,7 +18,7 @@ import {
import { cn } from "@lib/utils";
import { useServer } from "../server";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { RepoLink, StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { useToast } from "@ui/use-toast";
@@ -23,7 +23,7 @@ import {
import { ServerTable } from "./table";
import { DeleteResource, NewResource, ResourcePageHeader } from "../common";
import { ActionWithDialog, ConfirmButton, StatusBadge } from "@components/util";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { ServerStatsMini } from "./stats-mini";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
@@ -1,7 +1,10 @@
import { useLocalStorage, usePermissions, useRead, useUser } from "@lib/hooks";
import { useServer } from ".";
import { ReactNode, useMemo } from "react";
import { MobileFriendlyTabsSelector } from "@ui/mobile-friendly-tabs";
import {
MobileFriendlyTabsSelector,
TabNoContent,
} from "@ui/mobile-friendly-tabs";
import { ServerStats } from "./stats";
import { ServerInfo } from "./info";
import { ServerConfig } from "./config";
@@ -14,10 +17,10 @@ import { ServerTerminals } from "@components/terminal/server";
import { Card, CardHeader, CardTitle } from "@ui/card";
import { Types } from "komodo_client";
type ServerTabView = "Config" | "Stats" | "Docker" | "Resources" | "Terminals";
type ServerTabsView = "Config" | "Stats" | "Docker" | "Resources" | "Terminals";
export const ServerTabs = ({ id }: { id: string }) => {
const [view, setView] = useLocalStorage<ServerTabView>(
const [view, setView] = useLocalStorage<ServerTabsView>(
`server-${id}-tab`,
"Config"
);
@@ -45,36 +48,38 @@ export const ServerTabs = ({ id }: { id: string }) => {
const noResources = noDeployments && noRepos && noStacks;
const Selector = useMemo(
() => (
<MobileFriendlyTabsSelector
tabs={[
{
value: "Config",
},
{
value: "Stats",
},
{
value: "Docker",
},
{
value: "Resources",
disabled: noResources,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
]}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
),
const tabs = useMemo<TabNoContent<ServerTabsView>[]>(
() => [
{
value: "Config",
},
{
value: "Stats",
},
{
value: "Docker",
},
{
value: "Resources",
disabled: noResources,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
],
[noResources, terminalDisabled]
);
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
);
switch (view) {
case "Config":
return <ServerConfig id={id} titleOther={Selector} />;
@@ -42,6 +42,8 @@ import {
import { LinkedRepoConfig } from "@components/config/linked_repo";
import { Button } from "@ui/button";
import { Input } from "@ui/input";
import { Label } from "@ui/label";
import { Switch } from "@ui/switch";
import { useStack } from ".";
import { filterBySplit } from "@lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
@@ -244,19 +246,86 @@ export const StackConfig = ({
},
additional_env_files:
(mode === "Files On Server" || mode === "Git Repo") &&
((values, set) => (
<ConfigList
label="Additional Env Files"
boldLabel
addLabel="Add Env File"
description="Add additional env files to pass with '--env-file'. Relative to the 'Run Directory'."
field="additional_env_files"
values={values ?? []}
set={set}
disabled={disabled}
placeholder=".env"
/>
)),
((values, set) => {
const files = (values ?? []).map((v: any) =>
typeof v === "string" ? { path: v, track: true } : v
);
return (
<ConfigItem label="Additional Env Files" boldLabel>
<div className="flex flex-col gap-2 w-full">
{files.map((file: any, i: number) => (
<div key={i} className="flex items-center gap-4 w-full">
<Input
value={file.path || ""}
onChange={(e) => {
const newFiles = [...files];
newFiles[i] = {
path: e.target.value,
track: file.track ?? true,
};
set({ additional_env_files: newFiles });
}}
placeholder=".env"
disabled={disabled}
className="w-[400px] max-w-full"
/>
<div className="flex items-center gap-1.5">
<Switch
checked={file.track ?? true}
onCheckedChange={(track) => {
const newFiles = [...files];
newFiles[i] = { ...newFiles[i], track };
set({ additional_env_files: newFiles });
}}
disabled={disabled}
id={`track-${i}`}
/>
<Label htmlFor={`track-${i}`} className="text-sm">
Track
</Label>
</div>
{!disabled && (
<Button
variant="secondary"
onClick={() => {
set({
additional_env_files: files.filter(
(_: any, idx: number) => idx !== i
),
});
}}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<Button
variant="secondary"
onClick={() => {
set({
additional_env_files: [
...files,
{ path: "", track: true },
],
});
}}
className="w-fit"
>
<PlusCircle className="w-4 h-4 mr-2" />
Add Env File
</Button>
)}
<div className="text-sm text-muted-foreground">
Add additional env files to pass with '--env-file'. Relative
to the 'Run Directory'. Uncheck 'Track' for externally managed
files (e.g., sops decrypted).
</div>
</div>
</ConfigItem>
);
}),
},
};
@@ -368,6 +437,26 @@ export const StackConfig = ({
),
},
},
{
label: "Command Wrapper",
description:
"Optional wrapper to execute 'docker compose up -d' as a subcommand of tools like secrets management.",
components: {
compose_cmd_wrapper: (value, set) => (
<MonacoEditor
value={
value ??
"# sops exec-env .encrypted.env '[[COMPOSE_COMMAND]]'\n"
}
language="shell"
onValueChange={(compose_cmd_wrapper) =>
set({ compose_cmd_wrapper })
}
readOnly={disabled}
/>
),
},
},
{
label: "Extra Args",
labelHidden: true,
@@ -35,7 +35,7 @@ import {
import { Badge } from "@ui/badge";
import { Button } from "@ui/button";
import { useToast } from "@ui/use-toast";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipTrigger, TooltipContent } from "@ui/tooltip";
@@ -36,7 +36,7 @@ export const StackTabs = ({ id }: { id: string }) => {
? "Config"
: _view;
const tabsNoContent = useMemo<TabNoContent<StackTabsView>[]>(
const tabs = useMemo<TabNoContent<StackTabsView>[]>(
() => [
{
value: "Config",
@@ -59,7 +59,7 @@ export const StackTabs = ({ id }: { id: string }) => {
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabsNoContent}
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
@@ -0,0 +1,135 @@
import { Config } from "@components/config";
import { ConfigItem, ConfigList } from "@components/config/util";
import { useLocalStorage, usePermissions, useRead, useWrite } from "@lib/hooks";
import { Types } from "komodo_client";
import { ResourceSelector } from "../common";
import { Button } from "@ui/button";
import { MinusCircle, PlusCircle } from "lucide-react";
import { ReactNode } from "react";
export const SwarmConfig = ({
id,
titleOther,
}: {
id: string;
titleOther: ReactNode;
}) => {
const { canWrite } = usePermissions({ type: "Swarm", id });
const swarm = useRead("GetSwarm", { swarm: id }).data;
const config = swarm?.config;
const global_disabled =
useRead("GetCoreInfo", {}).data?.ui_write_disabled ?? false;
const [update, set] = useLocalStorage<Partial<Types.SwarmConfig>>(
`swarm-${id}-update-v1`,
{}
);
const { mutateAsync } = useWrite("UpdateSwarm");
if (!config) return null;
const disabled = global_disabled || !canWrite;
return (
<Config
titleOther={titleOther}
disabled={disabled}
original={config}
update={update}
set={set}
onSave={async () => {
await mutateAsync({ id, config: update });
}}
components={{
"": [
{
label: "Managers",
labelHidden: true,
components: {
server_ids: (server_ids, set) => {
return (
<ConfigItem
label="Manager Nodes"
boldLabel
description="Select the Servers which have joined the Swarm as Manager Nodes."
>
<div className="flex flex-col gap-4 w-full">
{server_ids?.map((server_id, index) => (
<div key={index} className="w-full flex gap-4">
<ResourceSelector
type="Server"
exclude_ids={server_ids}
selected={server_id}
onSelect={(server_id) =>
set({
server_ids: [
...server_ids.map((id, i) =>
i === index ? server_id : id
),
],
})
}
disabled={disabled}
align="start"
/>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
server_ids: [
...server_ids?.filter(
(_, i) => i !== index
),
],
})
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
server_ids: [...(server_ids ?? []), ""],
})
}
className="flex items-center gap-2 w-[200px]"
>
<PlusCircle className="w-4 h-4" />
Add Server
</Button>
)}
</div>
</ConfigItem>
);
},
},
},
{
label: "Links",
labelHidden: true,
components: {
links: (values, set) => (
<ConfigList
label="Links"
boldLabel
addLabel="Add Link"
description="Add quick links in the resource header"
field="links"
values={values ?? []}
set={set}
disabled={disabled}
placeholder="Input link"
/>
),
},
},
],
}}
/>
);
};
@@ -0,0 +1,189 @@
import { useRead } from "@lib/hooks";
import { RequiredResourceComponents } from "@types";
import {
Component,
Diamond,
FolderCode,
KeyRound,
ListTodo,
Settings,
SquareStack,
} from "lucide-react";
import { DeleteResource, NewResource, ResourcePageHeader } from "../common";
import { SwarmTable } from "./table";
import {
swarm_state_intention,
stroke_color_class_by_intention,
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
import { SwarmTabs } from "./tabs";
import { Link } from "react-router-dom";
export const useSwarm = (id?: string) =>
useRead("ListSwarms", {}, { refetchInterval: 10_000 }).data?.find(
(d) => d.id === id
);
export const useFullSwarm = (id: string) =>
useRead("GetSwarm", { swarm: id }, { refetchInterval: 10_000 }).data;
const SwarmIcon = ({ id, size }: { id?: string; size: number }) => {
const state = useSwarm(id)?.info.state;
const color = stroke_color_class_by_intention(swarm_state_intention(state));
return <Component className={cn(`w-${size} h-${size}`, state && color)} />;
};
export const SwarmComponents: RequiredResourceComponents = {
list_item: (id) => useSwarm(id),
resource_links: (resource) => (resource.config as Types.SwarmConfig).links,
Description: () => <>Control and monitor docker swarms.</>,
Dashboard: () => {
const summary = useRead("GetSwarmsSummary", {}).data;
return (
<DashboardPieChart
data={[
{ intention: "Good", value: summary?.healthy ?? 0, title: "Healthy" },
{
intention: "Critical",
value: summary?.unhealthy ?? 0,
title: "Unhealthy",
},
{
intention: "Unknown",
value: summary?.unknown ?? 0,
title: "Unknown",
},
]}
/>
);
},
New: () => <NewResource type="Swarm" />,
GroupActions: () => <GroupActions type="Swarm" actions={[]} />,
Table: ({ resources }) => (
<SwarmTable swarms={resources as Types.SwarmListItem[]} />
),
Icon: ({ id }) => <SwarmIcon id={id} size={4} />,
BigIcon: ({ id }) => <SwarmIcon id={id} size={8} />,
State: ({ id }) => {
const state = useSwarm(id)?.info.state;
return <StatusBadge text={state} intent={swarm_state_intention(state)} />;
},
Info: {},
Status: {
Err: ({ id }) => {
const err = useSwarm(id)?.info.err;
if (!err) return null;
return (
<Tooltip>
<TooltipTrigger asChild>
<Card className="px-3 py-2 bg-destructive/75 hover:bg-destructive transition-colors cursor-pointer">
<div className="text-sm text-nowrap overflow-hidden overflow-ellipsis">
Error
</div>
</Card>
</TooltipTrigger>
<TooltipContent className="w-fit max-w-[90vw] md:max-w-[60vw]">
<pre
dangerouslySetInnerHTML={{
__html: updateLogToHtml(err),
}}
/>
</TooltipContent>
</Tooltip>
);
},
},
Actions: {},
Page: {},
Config: SwarmTabs,
DangerZone: ({ id }) => <DeleteResource type="Swarm" id={id} />,
ResourcePageHeader: ({ id }) => {
const swarm = useSwarm(id);
return (
<ResourcePageHeader
intent={swarm_state_intention(swarm?.info.state)}
icon={<SwarmIcon id={id} size={8} />}
type="Swarm"
id={id}
resource={swarm}
state={swarm?.info.state}
status=""
/>
);
},
};
export type SwarmResourceType =
| "Node"
| "Service"
| "Task"
| "Secret"
| "Config"
| "Stack";
export const SWARM_ICONS: {
[type in SwarmResourceType]: React.FC<{ size?: number; className?: string }>;
} = {
Node: ({ size, className }) => (
<Diamond className={cn(`w-${size} h-${size}`, className)} />
),
Service: ({ size, className }) => (
<FolderCode className={cn(`w-${size} h-${size}`, className)} />
),
Task: ({ size, className }) => (
<ListTodo className={cn(`w-${size} h-${size}`, className)} />
),
Secret: ({ size, className }) => (
<KeyRound className={cn(`w-${size} h-${size}`, className)} />
),
Config: ({ size, className }) => (
<Settings className={cn(`w-${size} h-${size}`, className)} />
),
Stack: ({ size, className }) => (
<SquareStack className={cn(`w-${size} h-${size}`, className)} />
),
};
export const SwarmLink = ({
type,
swarm_id,
resource_id,
name,
}: {
type: SwarmResourceType;
swarm_id: string;
resource_id: string | undefined;
name: string | undefined;
}) => {
const Icon = SWARM_ICONS[type];
return (
<Link
to={`/swarms/${swarm_id}/swarm-${type.toLowerCase()}/${resource_id}`}
className="flex gap-2 items-center hover:underline"
>
<Icon size={4} />
{name ?? "Unknown"}
</Link>
);
};

Some files were not shown because too many files have changed in this diff Show More