Compare commits

..
156 Commits
Author SHA1 Message Date
mbecker20 54d167fe1e add config -> cf alias 2025-08-10 20:19:44 -07:00
mbecker20 6b6743876e con -> cn 2025-08-10 20:18:41 -07:00
mbecker20 38a4c4a470 deploy 1.19.0-dev-9 2025-08-10 20:13:51 -07:00
mbecker20 84368411ca The incoming arguments 2025-08-10 19:32:23 -07:00
mbecker20 e41f350776 webhook branch / body optional 2025-08-10 19:31:42 -07:00
mbecker20 3fef8644cc no space at front of KeyValue default args 2025-08-10 19:28:08 -07:00
mbecker20 baef9cc1b1 deploy 1.19.0-dev-8 2025-08-10 19:18:39 -07:00
mbecker20 7c2403b7d1 deploy 1.19.0-dev-7 2025-08-10 18:52:01 -07:00
mbecker20 95ffa5b4f5 deploy 1.19.0-dev-6 2025-08-10 18:36:03 -07:00
mbecker20 72a095f6be backend for Action arguments 2025-08-10 18:35:31 -07:00
mbecker20 67c869931f links in tables 2025-08-10 17:18:13 -07:00
mbecker20 42ea59aa23 deploy 1.19.0-dev-5 2025-08-10 16:24:35 -07:00
mbecker20 3abbab87e6 add links to table names 2025-08-10 16:23:58 -07:00
mbecker20 d16ebbe526 update fe w/ new contianer info minimal serialization 2025-08-10 16:10:28 -07:00
mbecker20 b44a6f7e57 just move to standard config dir .config/komodo/komodo.cli.* 2025-08-10 15:39:51 -07:00
mbecker20 1f078a0432 use include-based file finding for much faster discovery 2025-08-10 15:37:12 -07:00
mbecker20 7f9b7aa6cf tweak 2025-08-10 02:06:57 -07:00
mbecker20 96aefa75a9 dev-4 fix container parsing issue 2025-08-10 01:34:56 -07:00
mbecker20 9cf2df74ef fix: need serde default 2025-08-10 01:33:05 -07:00
mbecker20 47f5e5bd37 deploy 1.19.0-dev-3 2025-08-10 00:59:39 -07:00
mbecker20 51683612f5 km con inspect 2025-08-10 00:58:36 -07:00
mbecker20 108c9215d1 list resource apis 2025-08-10 00:13:53 -07:00
mbecker20 1fd3836a4a km ls 2025-08-09 19:18:33 -07:00
mbecker20 b765e9576d km ps initial 2025-08-09 16:05:27 -07:00
mbecker20 cea6fd2038 update docsite deps 2025-08-09 13:15:34 -07:00
mbecker20 60c3eda08a split the runfile commands 2025-08-09 13:15:28 -07:00
mbecker20 b09419cd4b tweak doc 2025-08-09 03:06:53 -07:00
mbecker20 13ff216ea7 tweak cli docs 2025-08-09 02:46:08 -07:00
mbecker20 d56d34c448 prog on cli docs 2025-08-09 02:41:25 -07:00
mbecker20 d0da77a6b8 improve km --help 2025-08-09 01:27:29 -07:00
mbecker20 7725cfa5bd no need to panic if empty config paths 2025-08-09 00:45:52 -07:00
mbecker20 cc5d947412 better config merge with base 2025-08-09 00:36:35 -07:00
mbecker20 3badb678db add default komodo cli config 2025-08-09 00:08:42 -07:00
mbecker20 f8af6da1a2 deploy 1.19.0-dev-2 2025-08-08 23:03:00 -07:00
mbecker20 dca0ae772b deploy 1.19.0-dev-1 2025-08-08 22:40:00 -07:00
mbecker20 558fb7bade config support yaml 2025-08-08 22:33:21 -07:00
mbecker20 cb7943ed5b core support reading from multiple config files 2025-08-08 22:24:59 -07:00
mbecker20 92967d3278 move to 1.19.0 2025-08-08 21:02:09 -07:00
itsmesidandGitHub b796dc8398 docs(build-images): document multi-platform builds with Docker Buildx (#721)
* docs(build-images): add multi-platform buildx guide to builders.md

* docs(build-images): add multi-platform buildx guide and clarify platform selection in Komodo UI Extra Args field
2025-08-08 21:00:26 -07:00
mbecker20 7bdbaca4b4 add internet interface example 2025-08-08 20:59:19 -07:00
Marcel PfennigandGitHub a90971eea6 Add manual network interface configuration for multi-NIC Docker environments (#719)
* Add iproute2 to debian-debs

* feat: Add manual network interface configuration for multi-NIC support

Complete implementation of manual interface configuration:
- Add internet_interface config option
- Implement manual gateway routing
- Add NET_ADMIN capability requirement
- Clean up codebase changes

* fix: Update internet interface handling for multi-NIC support

* refactor: Enhance error messages and logging in networking module

* refactor: Simplify interface argument handling and improve logging in network configuration and cleanup

* refactor(network): simplify startup integration and improve error handling

- Move config access and error handling into network::configure_internet_gateway()
- Simplify startup.rs to single function call without parameters
- Remove redundant check_network_privileges() function
- Improve error handling by checking actual command output instead of pre-validation
- Better separation of concerns between startup and network modules

Addresses feedback from PR discussion:
https://github.com/moghtech/komodo/pull/719#discussion_r2261542921

* fix(config): update default internet interface setting
Addresses feedback from PR discussion:
https://github.com/moghtech/komodo/pull/719#discussion_r2261552279

* fix(config): remove custom default for internet interface in CoreConfig

* move mod.rs -> network.rs
Addresses feedback from PR discussion:
https://github.com/moghtech/komodo/pull/719#discussion_r2261558332
2025-08-08 20:58:23 -07:00
mbecker20 5643424ed8 cli: user password reset, update user super admin 2025-08-08 22:19:46 -04:00
mbecker20 b1f5cccc0a add log when target db indexing disabled 2025-08-08 20:50:40 -04:00
mbecker20 e82a268861 deploy 1.18.5-dev-26 2025-08-08 20:35:24 -04:00
mbecker20 77b3c6d325 remove second schedule updating loop, can causes some schedules to be missed 2025-08-08 20:34:48 -04:00
mbecker20 9f90a913fc add runfile command aliases 2025-08-08 20:04:04 -04:00
mbecker20 76e84ddd6e db restore / copy index optional 2025-08-08 00:41:26 -07:00
mbecker20 2dd759e375 some more skip serialization 2025-08-07 18:31:02 -07:00
mbecker20 b2b9df0e2a correct defaults for CLI and only can pass restore folder from cli arg 2025-08-07 18:28:47 -07:00
mbecker20 6a42d47b6f km cfg --debug print mode 2025-08-07 18:12:36 -07:00
mbecker20 ca440c14b6 skip serialize default db / log configs 2025-08-07 18:09:52 -07:00
mbecker20 f2478de853 skip serializing profiles when empty 2025-08-07 21:01:42 -04:00
mbecker20 ebb4ea3c87 include commit variables 2025-08-07 20:59:27 -04:00
mbecker20 77ffb14f7d km cfg -a (print all profiles) 2025-08-07 20:56:49 -04:00
mbecker20 7430798f31 1.89 docker images not ready yet 2025-08-07 20:42:12 -04:00
mbecker20 881542a21c deploy 1.18.5-dev-25 2025-08-07 20:39:40 -04:00
mbecker20 0857583778 remove layouts overflow-x-scroll 2025-08-07 17:38:05 -07:00
mbecker20 7d9eab92e7 fix clippy lints re 1.89 2025-08-07 20:05:22 -04:00
mbecker20 d9abbbadaf skip serializing for proper merge 2025-08-07 19:41:24 -04:00
mbecker20 877a013fc0 rust 1.89 and improve config logs 2025-08-07 19:26:56 -04:00
mbecker20 a6811783ec fix page set titles 2025-08-07 19:14:03 -04:00
mbecker20 7c406fd9db cli: profiles support aliases and merge on top of Default (root) config 2025-08-07 19:06:44 -04:00
mbecker20 c84083062c auto version increment and deploy 2025-08-07 08:37:43 -04:00
mbecker20 4efc27b4db deploy 1.18.5-dev-24 2025-08-07 08:17:42 -04:00
Sam EdwardsandGitHub 9f413fec21 Frontend mobile fixes (#714)
* Allow ResourcePageHeader items to wrap

* Allow CardHeader items to wrap

* Increase z-index of sticky TableHeader, fixes #690

* Remove fixed widths from ActionButton, let them flex more to fit more layouts

* Make Section scroll overflow

* Remove grid class from Tabs, seems to prevent them from overflowing at small sizes
2025-08-07 04:50:33 -07:00
mbecker20 e8e1d1dfbc install-cli.py 2025-08-07 07:44:36 -04:00
mbecker20 bceb1e6ebf dev-23 use indexmap 2025-08-07 05:17:21 -04:00
mbecker20 0222ad3b3c dev-22 test the deploy 2025-08-07 03:44:58 -04:00
mbecker20 3dad87e18f dev 21 support cli update link 2025-08-07 02:48:21 -04:00
mbecker20 1a5779ca14 add update page 2025-08-06 23:38:34 -07:00
mbecker20 a24fefca52 dev 20 2025-08-07 01:52:57 -04:00
mbecker20 25e957ef0c implement update for most resources 2025-08-07 01:03:25 -04:00
mbecker20 0629744356 time the executions 2025-08-06 23:10:37 -04:00
mbecker20 5f6469cb01 improve cli initial logs 2025-08-06 22:39:49 -04:00
mbecker20 7b502cc6ca cli update variable - "km set var VAR value" 2025-08-06 22:10:50 -04:00
mbecker20 2c328f27e4 db copy / restore uses idempotent upsert 2025-08-06 21:25:44 -04:00
mbecker20 34da2063f9 Implement automatic backup pruning, default 14 backups before prune 2025-08-06 20:50:03 -04:00
mbecker20 cac20e4ff2 add ignores log 2025-08-06 18:02:42 -04:00
mbecker20 6c17049cb7 support .kmignore 2025-08-06 17:53:23 -04:00
mbecker20 9f4b246e43 dev-18 2025-08-06 07:01:09 -04:00
mbecker20 5dd865a856 update dockerfile default docker cli config keywords 2025-08-06 06:54:50 -04:00
mbecker20 7293a7004a dev-17 config loading preserves CONFIG_PATHS precedence 2025-08-06 06:40:25 -04:00
mbecker20 b82ec53c33 /backup -> /backups 2025-08-06 06:27:57 -04:00
mbecker20 7ce1fdaf78 cli nice 2025-08-06 05:24:16 -04:00
mbecker20 ce0425befd remove repo cache mount 2025-08-06 03:08:40 -04:00
mbecker20 4cbd9d2ac7 cli execute polls updates until complete before logging 2025-08-05 22:46:35 -04:00
mbecker20 8b92d1e435 dev-16 2025-08-05 18:09:31 -04:00
mbecker20 593967b6c7 update croner to latest, use static parser 2025-08-05 18:09:12 -04:00
mbecker20 e2a38a75f8 bump deps 2025-08-05 17:54:55 -04:00
mbecker20 9847b9499a dev-15 2025-08-05 06:38:00 -04:00
mbecker20 2e1d001bfe fix --yes 2025-08-05 06:37:19 -04:00
mbecker20 65be710adc wait for enter on --yes empty println 2025-08-05 06:32:13 -04:00
mbecker20 f122a3e18b tweaks for the cli logs 2025-08-05 06:28:17 -04:00
mbecker20 250bc017ca rename cli to km 2025-08-05 06:14:54 -04:00
mbecker20 4f93ef4d52 fix terminal reconnect issue 2025-08-05 01:52:42 -07:00
mbecker20 9c871497e4 gen client 2025-08-05 01:07:40 -07:00
mbecker20 fa5ac54243 tweak cli 2025-08-05 04:03:38 -04:00
mbecker20 3720c92908 command variant fields need to be #[arg] 2025-08-05 02:33:32 -04:00
mbecker20 aa99982687 dev-13 logs 2025-08-05 01:16:56 -04:00
mbecker20 232437a9d8 dev-12 implement backup using cli 2025-08-05 00:32:18 -04:00
mbecker20 ea0381ad41 dev-11 2025-08-04 05:04:48 -04:00
mbecker20 10476e561d consolidate and standardize cli 2025-08-04 05:03:29 -04:00
mbecker20 f3e77ccffe clippy lint 2025-08-03 21:40:13 -04:00
mbecker20 c94d83bc3a use database library 2025-08-02 21:01:13 -04:00
mbecker20 9ede3977c5 fmt 2025-08-02 20:17:41 -04:00
160709788a add user agent to oidc requests (#701)
Co-authored-by: eleith <online-github@eleith.com>
2025-08-02 17:16:28 -07:00
mbecker20 3c89a72841 dev-10 enable fancy toml 2025-08-02 20:04:23 -04:00
mbecker20 9e71f008df enable_fance_toml 2025-08-02 20:03:54 -04:00
mbecker20 c4cbfada00 dev-9 2025-08-01 05:41:09 -04:00
mbecker20 24890e8aed rename to komodo-util 2025-08-01 05:39:39 -04:00
mbecker20 5a272d1e98 clean up logs 2025-08-01 05:34:14 -04:00
mbecker20 1444e803de clean up 2025-08-01 05:06:44 -04:00
mbecker20 dd59f56b6d dev-8 2025-08-01 05:06:13 -04:00
mbecker20 4729dc2e3c back to LinesCodec 2025-08-01 05:05:38 -04:00
mbecker20 178203c07c dev-7 2025-08-01 04:49:27 -04:00
mbecker20 919ae99e2c core image include util binary 2025-08-01 04:49:02 -04:00
mbecker20 62ea22af7f cleanup restore 2025-08-01 04:35:57 -04:00
mbecker20 d17ba3f9c4 refactor backup/restore duplication 2025-08-01 04:30:55 -04:00
mbecker20 03f3b2c80d util backup / restore 2025-08-01 04:18:03 -04:00
mbecker20 e2c52fea6b combine users page search 2025-07-31 17:17:01 -04:00
mbecker20 ae9f2d9f74 tag include unused flag for future use 2025-07-31 17:10:36 -04:00
mbecker20 842c67bb50 Komodo managed env-file should take precedence in all cases (ie come last in env file list) 2025-07-31 13:19:11 -04:00
mbecker20 056050e534 fix execute usage docs 2025-07-31 05:22:45 -04:00
mbecker20 88ac4cf9dd Add Clear repo cache method 2025-07-31 01:25:06 -04:00
mbecker20 c58ec67f0a searchable container stats, toggle collaple container / disk sections 2025-07-30 23:53:47 -04:00
mbecker20 2ba2cb1e0b server stats UI: move current disk breakdown above charts 2025-07-30 23:39:59 -04:00
mbecker20 ebe9bd75ad forgiving vec log. allowed ips docs 2025-07-28 21:21:42 -04:00
mbecker20 4faf031b8a dev-6 2025-07-28 01:52:07 -04:00
mbecker20 738c347155 forgiving vec 2025-07-28 01:51:41 -04:00
Brian BradleyandGitHub 4c1d7a61dc Allow CIDR ranges in Allowed IPs (#666)
* Allow CIDR ranges in Allowed IPs

* Catch mixed IPv4/IPv6 mappings that are probably intended to match
2025-07-28 01:03:12 -04:00
DanielandGitHub 7af8e5b287 Fix Google Login enabled message (#668)
- it was showing "Github Login" instead of "Google Login"
2025-07-28 00:58:09 -04:00
mbecker20 8bf4ea482a fmt 2025-07-28 00:57:16 -04:00
mbecker20 527b300caa startup use action query 2025-07-28 00:57:12 -04:00
Brian BradleyandGitHub d0067f0d2b Implement ability to run actions at startup (#664)
* Implement ability to run actions at startup

* run post-startup actions after server is listening
2025-07-28 00:49:31 -04:00
mbecker20 cca040c718 reorder resource sync config 2025-07-27 23:43:09 -04:00
mbecker20 63be48e2fb fix configure build extra args 2025-07-27 15:36:14 -04:00
mbecker20 d723515d28 refetch given in ms 2025-07-26 22:08:00 -04:00
mbecker20 0c34695d1a forgot to pipe through poll in previous refactor 2025-07-26 22:02:37 -04:00
mbecker20 3c4551ae19 dev-5 2025-07-26 02:20:32 -04:00
mbecker20 da5ac544f6 fix status cache not cleaning on resource delete 2025-07-26 01:36:33 -04:00
mbecker20 c3dc710598 add "ScheduleRun" 2025-07-24 23:58:51 -04:00
mbecker20 6c3b7cc30b Fix missing alert types for whitelist 2025-07-24 23:50:25 -04:00
mbecker20 8b19fdba40 webhook disabled early return 2025-07-18 03:53:37 -04:00
mbecker20 5e3ef5910a dev-4 2025-07-17 03:05:19 -04:00
mbecker20 08e2c51301 bump svi with PR fix 2025-07-17 03:04:33 -04:00
mbecker20 7943655fb8 emphasize ferret version pinning 2025-07-17 03:04:08 -04:00
mbecker20 e22eda939c dev-3 2025-07-12 14:19:00 -04:00
mbecker20 a28892e726 clippy fix komodo_client 2025-07-12 13:27:08 -04:00
mbecker20 295f9927d4 clippy lints periphery 2025-07-12 13:25:54 -04:00
mbecker20 a9aad11a51 clippy auto fix 2025-07-12 13:24:23 -04:00
mbecker20 aafb5a68ed repo based stack commit happens from core repo cache rather than on server to simplify 2025-07-12 13:21:16 -04:00
mbecker20 1e4c7b8498 tweaks 2025-07-12 13:20:43 -04:00
mbecker20 aca4c09bf8 bump rust to 1.88 2025-07-12 02:04:27 -04:00
mbecker20 5af8dd4553 dev-2 2025-07-12 02:00:03 -04:00
mbecker20 75627901ce prevent empty additional permission check (ie for new resources) 2025-07-12 01:44:59 -04:00
mbecker20 adcb0c0755 start 1.18.5 2025-06-24 20:09:18 -04:00
127 changed files with 1419 additions and 3469 deletions
Generated
+54 -54
View File
@@ -106,9 +106,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.99"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arc-swap"
@@ -178,9 +178,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-config"
version = "1.8.5"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c478f5b10ce55c9a33f87ca3404ca92768b144fc1bfdede7c0121214a8283a25"
checksum = "483020b893cdef3d89637e428d588650c71cfae7ea2e6ecbaee4de4ff99fb2dd"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -267,9 +267,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.159.0"
version = "1.156.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed753fc534bbb68760d5b2e3e46f81e5ccb9a96d8359b9ce793e123cb3073e8e"
checksum = "0e04d00427a65f27b7f704f43352560d1eba7d31204a9566e2c633fb5c1d96ed"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -290,9 +290,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.81.0"
version = "1.79.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79ede098271e3471036c46957cba2ba30888f53bda2515bf04b560614a30a36e"
checksum = "0a847168f15b46329fa32c7aca4e4f1a2e072f9b422f0adb19756f2e1457f111"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -312,9 +312,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.82.0"
version = "1.80.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43326f724ba2cc957e6f3deac0ca1621a3e5d4146f5970c24c8a108dac33070f"
checksum = "b654dd24d65568738593e8239aef279a86a15374ec926ae8714e2d7245f34149"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -334,9 +334,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.83.0"
version = "1.81.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5468593c47efc31fdbe6c902d1a5fde8d9c82f78a3f8ccfe907b1e9434748cb"
checksum = "c92ea8a7602321c83615c82b408820ad54280fb026e92de0eeea937342fafa24"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -828,7 +828,7 @@ dependencies = [
"serde_json",
"serde_repr",
"serde_urlencoded",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tokio",
"tokio-util",
"tower-service",
@@ -904,7 +904,7 @@ dependencies = [
[[package]]
name = "cache"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"tokio",
@@ -1071,7 +1071,7 @@ dependencies = [
[[package]]
name = "command"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"komodo_client",
"run_command",
@@ -1080,7 +1080,7 @@ dependencies = [
[[package]]
name = "config"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"colored",
"indexmap 2.10.0",
@@ -1088,7 +1088,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml_ng",
"thiserror 2.0.14",
"thiserror 2.0.12",
"toml",
"wildcard",
]
@@ -1320,7 +1320,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "database"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"async-compression",
@@ -1619,7 +1619,7 @@ dependencies = [
[[package]]
name = "environment"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"formatting",
@@ -1629,9 +1629,9 @@ dependencies = [
[[package]]
name = "environment_file"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"thiserror 2.0.14",
"thiserror 2.0.12",
]
[[package]]
@@ -1719,7 +1719,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"serror",
]
@@ -1881,7 +1881,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"cache",
@@ -2487,7 +2487,7 @@ dependencies = [
[[package]]
name = "interpolate"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"komodo_client",
@@ -2618,7 +2618,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"chrono",
@@ -2643,7 +2643,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2666,7 +2666,7 @@ dependencies = [
"serde_qs",
"serror",
"strum 0.27.2",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tokio",
"tokio-tungstenite 0.27.0",
"tokio-util",
@@ -2677,7 +2677,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"arc-swap",
@@ -2747,7 +2747,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"arc-swap",
@@ -2868,7 +2868,7 @@ dependencies = [
[[package]]
name = "logger"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"komodo_client",
@@ -3017,7 +3017,7 @@ dependencies = [
"mongo_indexed_derive",
"mongodb",
"serde",
"thiserror 2.0.14",
"thiserror 2.0.12",
]
[[package]]
@@ -3369,7 +3369,7 @@ dependencies = [
"futures-sink",
"js-sys",
"pin-project-lite",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tracing",
]
@@ -3399,7 +3399,7 @@ dependencies = [
"opentelemetry_sdk",
"prost",
"reqwest",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tonic",
"tracing",
]
@@ -3435,7 +3435,7 @@ dependencies = [
"percent-encoding",
"rand 0.9.2",
"serde_json",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
]
@@ -3610,7 +3610,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "periphery_client"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"komodo_client",
@@ -3807,7 +3807,7 @@ dependencies = [
"rustc-hash 2.1.1",
"rustls 0.23.31",
"socket2 0.5.10",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tokio",
"tracing",
"web-time",
@@ -3828,7 +3828,7 @@ dependencies = [
"rustls 0.23.31",
"rustls-pki-types",
"slab",
"thiserror 2.0.14",
"thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
@@ -4141,7 +4141,7 @@ dependencies = [
[[package]]
name = "response"
version = "1.19.0"
version = "1.19.0-dev-9"
dependencies = [
"anyhow",
"axum",
@@ -4599,7 +4599,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352"
dependencies = [
"percent-encoding",
"serde",
"thiserror 2.0.14",
"thiserror 2.0.12",
]
[[package]]
@@ -4782,7 +4782,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [
"num-bigint",
"num-traits",
"thiserror 2.0.14",
"thiserror 2.0.12",
"time",
]
@@ -4931,7 +4931,7 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91bb902c47385644774ba89f7e7982d3a32cf1da4e8870bf633cdece450eced1"
dependencies = [
"thiserror 2.0.14",
"thiserror 2.0.12",
]
[[package]]
@@ -4978,9 +4978,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.0"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [
"libc",
"memchr",
@@ -5034,11 +5034,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.14"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.14",
"thiserror-impl 2.0.12",
]
[[package]]
@@ -5054,9 +5054,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.14"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -5277,14 +5277,14 @@ dependencies = [
[[package]]
name = "toml_pretty"
version = "1.2.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd195be7600b4b26e951a25b382b67a976d2d44bb967a1a5b573a3e63d18c29"
checksum = "dd412f79539d72f97ac0ec9905b0178be1be4f255ddc2ae6f1ff3f7b234bb870"
dependencies = [
"ordered_hash_map",
"serde",
"serde_json",
"thiserror 2.0.14",
"thiserror 1.0.69",
]
[[package]]
@@ -5488,7 +5488,7 @@ dependencies = [
"log",
"rand 0.9.2",
"sha1",
"thiserror 2.0.14",
"thiserror 2.0.12",
"utf-8",
]
@@ -5507,7 +5507,7 @@ dependencies = [
"rustls 0.23.31",
"rustls-pki-types",
"sha1",
"thiserror 2.0.14",
"thiserror 2.0.12",
"utf-8",
]
@@ -5862,7 +5862,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189"
dependencies = [
"thiserror 2.0.14",
"thiserror 2.0.12",
]
[[package]]
+7 -7
View File
@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "1.19.0"
version = "1.19.0-dev-9"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -42,7 +42,7 @@ 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 = "1.1.2"
mungos = "3.2.1"
svi = "1.2.0"
@@ -74,8 +74,8 @@ serde_qs = "0.15.0"
toml = "0.9.5"
# ERROR
anyhow = "1.0.99"
thiserror = "2.0.14"
anyhow = "1.0.98"
thiserror = "2.0.12"
# LOGGING
opentelemetry-otlp = { version = "0.30.0", features = ["tls-roots", "reqwest-rustls"] }
@@ -108,11 +108,11 @@ hex = "0.4.3"
# SYSTEM
portable-pty = "0.9.0"
bollard = "0.19.2"
sysinfo = "0.37.0"
sysinfo = "0.36.1"
# CLOUD
aws-config = "1.8.5"
aws-sdk-ec2 = "1.159.0"
aws-config = "1.8.4"
aws-sdk-ec2 = "1.156.0"
aws-credential-types = "1.2.5"
## CRON
+1 -1
View File
@@ -1,7 +1,7 @@
## Builds the Komodo Core, Periphery, and Util binaries
## for a specific architecture.
FROM rust:1.89.0-bullseye AS builder
FROM rust:1.88.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
-34
View File
@@ -1,34 +0,0 @@
## Builds the Komodo Core, Periphery, and Util binaries
## for a specific architecture.
## Uses chef for dependency caching to help speed up back-to-back builds.
FROM lukemathwalker/cargo-chef:latest-rust-1.89.0-bullseye AS chef
WORKDIR /builder
# Plan just the RECIPE to see if things have changed
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /builder/recipe.json recipe.json
# Build JUST dependencies - cached layer
RUN cargo chef cook --release --recipe-path recipe.json
# NOW copy again (this time into builder) and build app
COPY . .
RUN \
cargo build --release --bin core && \
cargo build --release --bin periphery && \
cargo build --release --bin km
# Copy just the binaries to scratch image
FROM scratch
COPY --from=builder /builder/target/release/core /core
COPY --from=builder /builder/target/release/periphery /periphery
COPY --from=builder /builder/target/release/km /km
LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo
LABEL org.opencontainers.image.description="Komodo Binaries"
LABEL org.opencontainers.image.licenses=GPL-3.0
+1 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.89.0-bullseye AS builder
FROM rust:1.88.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
+12 -12
View File
@@ -7,13 +7,13 @@ Can be used to move between MongoDB / FerretDB, or upgrade from FerretDB v1 to v
services:
copy_database:
image: ghcr.io/moghtech/komodo-cli
command: km database copy -y
image: ghcr.io/moghtech/komodo-util
environment:
KOMODO_DATABASE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@source:27017
KOMODO_DATABASE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
KOMODO_CLI_DATABASE_TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@target:27017
KOMODO_CLI_DATABASE_TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
MODE: CopyDatabase
SOURCE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@source:27017
SOURCE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@target:27017
TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
```
@@ -90,13 +90,13 @@ services:
...(new database)
copy_database:
image: ghcr.io/moghtech/komodo-cli
command: km database copy -y
image: ghcr.io/moghtech/komodo-util
environment:
KOMODO_DATABASE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb:27017/${KOMODO_DATABASE_DB_NAME:-komodo}?authMechanism=PLAIN
KOMODO_DATABASE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
KOMODO_CLI_DATABASE_TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb2:27017
KOMODO_CLI_DATABASE_TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
MODE: CopyDatabase
SOURCE_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb:27017/${KOMODO_DATABASE_DB_NAME:-komodo}?authMechanism=PLAIN
SOURCE_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
TARGET_URI: mongodb://${KOMODO_DB_USERNAME}:${KOMODO_DB_PASSWORD}@ferretdb2:27017
TARGET_DB_NAME: ${KOMODO_DATABASE_DB_NAME:-komodo}
...(unchanged)
```
+36 -67
View File
@@ -14,17 +14,15 @@ use komodo_client::{
config::cli::args::container::{
Container, ContainerCommand, InspectContainer,
},
docker::{
self,
container::{ContainerListItem, ContainerStateStatusEnum},
docker::container::{
ContainerListItem, ContainerStateStatusEnum,
},
},
};
use crate::{
command::{
PrintTable, clamp_sha, matches_wildcards, parse_wildcards,
print_items,
PrintTable, matches_wildcards, parse_wildcards, print_items,
},
config::cli_config,
};
@@ -42,7 +40,6 @@ async fn list_containers(
Container {
all,
down,
links,
reverse,
containers: names,
images,
@@ -129,7 +126,7 @@ async fn list_containers(
if *reverse {
containers.reverse();
}
print_items(containers, *format, *links)?;
print_items(containers, *format)?;
Ok(())
}
@@ -199,60 +196,38 @@ pub async fn inspect_container(
);
}
1 => {
println!("{}", serialize_container(inspect, &containers[0])?);
println!(
"{}",
serde_json::to_string_pretty(&containers[0])
.context("Failed to serialize items to JSON")?
);
}
_ => {
let containers = containers
.iter()
.map(|c| serialize_container(inspect, c))
.collect::<anyhow::Result<Vec<_>>>()?
.join("\n");
println!("{containers}");
println!(
"{}",
serde_json::to_string_pretty(&containers)
.context("Failed to serialize items to JSON")?
);
}
}
Ok(())
}
fn serialize_container(
inspect: &InspectContainer,
container: &docker::container::Container,
) -> anyhow::Result<String> {
let res = if inspect.state {
serde_json::to_string_pretty(&container.state)
} else if inspect.mounts {
serde_json::to_string_pretty(&container.mounts)
} else if inspect.host_config {
serde_json::to_string_pretty(&container.host_config)
} else if inspect.config {
serde_json::to_string_pretty(&container.config)
} else if inspect.network_settings {
serde_json::to_string_pretty(&container.network_settings)
} else {
serde_json::to_string_pretty(container)
}
.context("Failed to serialize items to JSON")?;
Ok(res)
}
// (Option<Server Name>, Container)
impl PrintTable for (Option<&'_ str>, ContainerListItem) {
fn header(links: bool) -> &'static [&'static str] {
if links {
&[
"Container",
"State",
"Server",
"Ports",
"Networks",
"Image",
"Link",
]
} else {
&["Container", "State", "Server", "Ports", "Networks", "Image"]
}
fn header() -> &'static [&'static str] {
&[
"Container",
"State",
"Server",
"Ports",
"Networks",
"Image",
"Link",
]
}
fn row(self, links: bool) -> Vec<Cell> {
fn row(self) -> Vec<Cell> {
let color = match self.1.state {
ContainerStateStatusEnum::Running => Color::Green,
ContainerStateStatusEnum::Paused => Color::DarkYellow,
@@ -282,21 +257,6 @@ impl PrintTable for (Option<&'_ str>, ContainerListItem) {
} else {
Cell::new(format!(":{}", ports.join(", :")))
};
let image = self.1.image.as_deref().unwrap_or("Unknown");
let mut res = vec![
Cell::new(self.1.name.clone()).add_attribute(Attribute::Bold),
Cell::new(self.1.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.0.unwrap_or("Unknown")),
ports,
Cell::new(networks.join(", ")),
Cell::new(clamp_sha(&image)),
];
if !links {
return res;
}
let link = if let Some(server_id) = self.1.server_id {
format!(
"{}/servers/{server_id}/container/{}",
@@ -306,7 +266,16 @@ impl PrintTable for (Option<&'_ str>, ContainerListItem) {
} else {
String::new()
};
res.push(Cell::new(link));
res
vec![
Cell::new(self.1.name).add_attribute(Attribute::Bold),
Cell::new(self.1.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.0.unwrap_or("Unknown")),
ports,
Cell::new(networks.join(", ")),
Cell::new(self.1.image.as_deref().unwrap_or("Unknown")),
Cell::new(link),
]
}
}
-14
View File
@@ -218,12 +218,6 @@ pub async fn handle(
Execution::ClearRepoCache(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BackupCoreDatabase(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::GlobalAutoUpdate(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::Sleep(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
@@ -472,14 +466,6 @@ pub async fn handle(
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BackupCoreDatabase(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::GlobalAutoUpdate(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::Sleep(request) => {
let duration =
Duration::from_millis(request.duration_ms as u64);
+120 -323
View File
@@ -51,38 +51,38 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
match &list.command {
None => list_all(list).await,
Some(ListCommand::Servers(filters)) => {
list_resources::<ServerListItem>(filters, false).await
list_resources::<ServerListItem>(filters).await
}
Some(ListCommand::Stacks(filters)) => {
list_resources::<StackListItem>(filters, false).await
list_resources::<StackListItem>(filters).await
}
Some(ListCommand::Deployments(filters)) => {
list_resources::<DeploymentListItem>(filters, false).await
list_resources::<DeploymentListItem>(filters).await
}
Some(ListCommand::Builds(filters)) => {
list_resources::<BuildListItem>(filters, false).await
list_resources::<BuildListItem>(filters).await
}
Some(ListCommand::Repos(filters)) => {
list_resources::<RepoListItem>(filters, false).await
list_resources::<RepoListItem>(filters).await
}
Some(ListCommand::Procedures(filters)) => {
list_resources::<ProcedureListItem>(filters, false).await
list_resources::<ProcedureListItem>(filters).await
}
Some(ListCommand::Actions(filters)) => {
list_resources::<ActionListItem>(filters, false).await
list_resources::<ActionListItem>(filters).await
}
Some(ListCommand::Syncs(filters)) => {
list_resources::<ResourceSyncListItem>(filters, false).await
}
Some(ListCommand::Builders(filters)) => {
list_resources::<BuilderListItem>(filters, false).await
}
Some(ListCommand::Alerters(filters)) => {
list_resources::<AlerterListItem>(filters, false).await
list_resources::<ResourceSyncListItem>(filters).await
}
Some(ListCommand::Schedules(filters)) => {
list_schedules(filters).await
}
Some(ListCommand::Builders(filters)) => {
list_resources::<BuilderListItem>(filters).await
}
Some(ListCommand::Alerters(filters)) => {
list_resources::<AlerterListItem>(filters).await
}
}
}
@@ -105,61 +105,61 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
.into_iter()
.map(|t| (t.id, t.name))
.collect::<HashMap<_, _>>())),
ServerListItem::list(client, &filters, true),
StackListItem::list(client, &filters, true),
DeploymentListItem::list(client, &filters, true),
BuildListItem::list(client, &filters, true),
RepoListItem::list(client, &filters, true),
ProcedureListItem::list(client, &filters, true),
ActionListItem::list(client, &filters, true),
ResourceSyncListItem::list(client, &filters, true),
ServerListItem::list(client, &filters),
StackListItem::list(client, &filters),
DeploymentListItem::list(client, &filters),
BuildListItem::list(client, &filters),
RepoListItem::list(client, &filters),
ProcedureListItem::list(client, &filters),
ActionListItem::list(client, &filters),
ResourceSyncListItem::list(client, &filters),
)?;
if !servers.is_empty() {
fix_tags(&mut servers, &tags);
print_items(servers, filters.format, list.links)?;
print_items(servers, filters.format)?;
println!();
}
if !stacks.is_empty() {
fix_tags(&mut stacks, &tags);
print_items(stacks, filters.format, list.links)?;
print_items(stacks, filters.format)?;
println!();
}
if !deployments.is_empty() {
fix_tags(&mut deployments, &tags);
print_items(deployments, filters.format, list.links)?;
print_items(deployments, filters.format)?;
println!();
}
if !builds.is_empty() {
fix_tags(&mut builds, &tags);
print_items(builds, filters.format, list.links)?;
print_items(builds, filters.format)?;
println!();
}
if !repos.is_empty() {
fix_tags(&mut repos, &tags);
print_items(repos, filters.format, list.links)?;
print_items(repos, filters.format)?;
println!();
}
if !procedures.is_empty() {
fix_tags(&mut procedures, &tags);
print_items(procedures, filters.format, list.links)?;
print_items(procedures, filters.format)?;
println!();
}
if !actions.is_empty() {
fix_tags(&mut actions, &tags);
print_items(actions, filters.format, list.links)?;
print_items(actions, filters.format)?;
println!();
}
if !syncs.is_empty() {
fix_tags(&mut syncs, &tags);
print_items(syncs, filters.format, list.links)?;
print_items(syncs, filters.format)?;
println!();
}
@@ -168,7 +168,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
async fn list_resources<T>(
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<()>
where
T: ListResources,
@@ -176,7 +175,7 @@ where
{
let client = crate::command::komodo_client().await?;
let (mut resources, tags) = tokio::try_join!(
T::list(client, filters, minimal),
T::list(client, filters),
client.read(ListTags::default()).map(|res| res.map(|res| res
.into_iter()
.map(|t| (t.id, t.name))
@@ -184,7 +183,7 @@ where
)?;
fix_tags(&mut resources, &tags);
if !resources.is_empty() {
print_items(resources, filters.format, filters.links)?;
print_items(resources, filters.format)?;
}
Ok(())
}
@@ -227,7 +226,7 @@ async fn list_schedules(
a.name.cmp(&b.name).then(a.enabled.cmp(&b.enabled))
});
if !schedules.is_empty() {
print_items(schedules, filters.format, filters.links)?;
print_items(schedules, filters.format)?;
}
Ok(())
}
@@ -255,8 +254,6 @@ where
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
// For use with root `km ls`
minimal: bool,
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
}
@@ -267,14 +264,12 @@ impl ListResources for ServerListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let servers = client
.read(ListServers {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?;
@@ -287,8 +282,6 @@ impl ListResources for ServerListItem {
true
} else if filters.down {
!matches!(server.info.state, ServerState::Ok)
} else if filters.in_progress {
false
} else {
matches!(server.info.state, ServerState::Ok)
};
@@ -310,7 +303,6 @@ impl ListResources for StackListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (servers, mut stacks) = tokio::try_join!(
client
@@ -325,7 +317,6 @@ impl ListResources for StackListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
)?;
@@ -346,17 +337,9 @@ impl ListResources for StackListItem {
let state_check = if filters.all {
true
} else if filters.down {
!matches!(
stack.info.state,
StackState::Running | StackState::Deploying
)
} else if filters.in_progress {
matches!(stack.info.state, StackState::Deploying)
!matches!(stack.info.state, StackState::Running)
} else {
matches!(
stack.info.state,
StackState::Running | StackState::Deploying
)
matches!(stack.info.state, StackState::Running)
};
state_check
&& matches_wildcards(&names, &[stack.name.as_str()])
@@ -382,7 +365,6 @@ impl ListResources for DeploymentListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (servers, mut deployments) = tokio::try_join!(
client
@@ -397,7 +379,6 @@ impl ListResources for DeploymentListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
)?;
@@ -419,17 +400,9 @@ impl ListResources for DeploymentListItem {
let state_check = if filters.all {
true
} else if filters.down {
!matches!(
deployment.info.state,
DeploymentState::Running | DeploymentState::Deploying
)
} else if filters.in_progress {
matches!(deployment.info.state, DeploymentState::Deploying)
!matches!(deployment.info.state, DeploymentState::Running)
} else {
matches!(
deployment.info.state,
DeploymentState::Running | DeploymentState::Deploying
)
matches!(deployment.info.state, DeploymentState::Running)
};
state_check
&& matches_wildcards(&names, &[deployment.name.as_str()])
@@ -455,7 +428,6 @@ impl ListResources for BuildListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (builders, mut builds) = tokio::try_join!(
client
@@ -470,7 +442,6 @@ impl ListResources for BuildListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
)?;
@@ -488,20 +459,7 @@ impl ListResources for BuildListItem {
let mut builds = builds
.into_iter()
.filter(|build| {
let state_check = if filters.all {
true
} else if filters.down {
matches!(
build.info.state,
BuildState::Failed | BuildState::Unknown
)
} else if minimal || filters.in_progress {
matches!(build.info.state, BuildState::Building)
} else {
true
};
state_check
&& matches_wildcards(&names, &[build.name.as_str()])
matches_wildcards(&names, &[build.name.as_str()])
&& matches_wildcards(
&builders,
&[build.info.builder_id.as_str()],
@@ -523,7 +481,6 @@ impl ListResources for RepoListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut repos = client
@@ -531,30 +488,11 @@ impl ListResources for RepoListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|repo| {
let state_check = if filters.all {
true
} else if filters.down {
matches!(
repo.info.state,
RepoState::Failed | RepoState::Unknown
)
} else if minimal || filters.in_progress {
matches!(
repo.info.state,
RepoState::Building | RepoState::Cloning
)
} else {
true
};
state_check
&& matches_wildcards(&names, &[repo.name.as_str()])
})
.filter(|repo| matches_wildcards(&names, &[repo.name.as_str()]))
.collect::<Vec<_>>();
repos.sort_by(|a, b| {
a.name
@@ -571,7 +509,6 @@ impl ListResources for ProcedureListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut procedures = client
@@ -579,26 +516,12 @@ impl ListResources for ProcedureListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|procedure| {
let state_check = if filters.all {
true
} else if filters.down {
matches!(
procedure.info.state,
ProcedureState::Failed | ProcedureState::Unknown
)
} else if minimal || filters.in_progress {
matches!(procedure.info.state, ProcedureState::Running)
} else {
true
};
state_check
&& matches_wildcards(&names, &[procedure.name.as_str()])
matches_wildcards(&names, &[procedure.name.as_str()])
})
.collect::<Vec<_>>();
procedures.sort_by(|a, b| {
@@ -619,7 +542,6 @@ impl ListResources for ActionListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut actions = client
@@ -627,26 +549,12 @@ impl ListResources for ActionListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|action| {
let state_check = if filters.all {
true
} else if filters.down {
matches!(
action.info.state,
ActionState::Failed | ActionState::Unknown
)
} else if minimal || filters.in_progress {
matches!(action.info.state, ActionState::Running)
} else {
true
};
state_check
&& matches_wildcards(&names, &[action.name.as_str()])
matches_wildcards(&names, &[action.name.as_str()])
})
.collect::<Vec<_>>();
actions.sort_by(|a, b| {
@@ -667,7 +575,6 @@ impl ListResources for ResourceSyncListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut syncs = client
@@ -675,30 +582,11 @@ impl ListResources for ResourceSyncListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|sync| {
let state_check = if filters.all {
true
} else if filters.down {
matches!(
sync.info.state,
ResourceSyncState::Failed | ResourceSyncState::Unknown
)
} else if minimal || filters.in_progress {
matches!(
sync.info.state,
ResourceSyncState::Syncing | ResourceSyncState::Pending
)
} else {
true
};
state_check
&& matches_wildcards(&names, &[sync.name.as_str()])
})
.filter(|sync| matches_wildcards(&names, &[sync.name.as_str()]))
.collect::<Vec<_>>();
syncs.sort_by(|a, b| {
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
@@ -712,7 +600,6 @@ impl ListResources for BuilderListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut builders = client
@@ -720,14 +607,12 @@ impl ListResources for BuilderListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|builder| {
(!minimal || filters.all)
&& matches_wildcards(&names, &[builder.name.as_str()])
matches_wildcards(&names, &[builder.name.as_str()])
})
.collect::<Vec<_>>();
builders.sort_by(|a, b| {
@@ -744,7 +629,6 @@ impl ListResources for AlerterListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
let mut syncs = client
@@ -752,15 +636,11 @@ impl ListResources for AlerterListItem {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
})
.await?
.into_iter()
.filter(|sync| {
(!minimal || filters.all)
&& matches_wildcards(&names, &[sync.name.as_str()])
})
.filter(|sync| matches_wildcards(&names, &[sync.name.as_str()]))
.collect::<Vec<_>>();
syncs.sort_by(|a, b| {
a.info
@@ -776,51 +656,39 @@ impl ListResources for AlerterListItem {
// TABLE
impl PrintTable for ResourceListItem<ServerListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Server", "State", "Address", "Tags", "Link"]
} else {
&["Server", "State", "Address", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Server", "State", "Address", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<Cell> {
fn row(self) -> Vec<Cell> {
let color = match self.info.state {
ServerState::Ok => Color::Green,
ServerState::NotOk => Color::Red,
ServerState::Disabled => Color::Blue,
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.info.address),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Server,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<StackListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Stack", "State", "Server", "Tags", "Link"]
} else {
&["Stack", "State", "Server", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Stack", "State", "Server", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
StackState::Down => Color::Blue,
StackState::Running => Color::Green,
StackState::Deploying => Color::DarkYellow,
StackState::Paused => Color::DarkYellow,
StackState::Unknown => Color::Magenta,
_ => Color::Red,
@@ -832,7 +700,7 @@ impl PrintTable for ResourceListItem<StackListItemInfo> {
// } else {
// "UI Defined"
// };
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
@@ -840,97 +708,75 @@ impl PrintTable for ResourceListItem<StackListItemInfo> {
Cell::new(self.info.server_id),
// Cell::new(source),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Stack,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<DeploymentListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Deployment", "State", "Server", "Tags", "Link"]
} else {
&["Deployment", "State", "Server", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Deployment", "State", "Server", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
DeploymentState::NotDeployed => Color::Blue,
DeploymentState::Running => Color::Green,
DeploymentState::Deploying => Color::DarkYellow,
DeploymentState::Paused => Color::DarkYellow,
DeploymentState::Unknown => Color::Magenta,
_ => Color::Red,
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.info.server_id),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Deployment,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<BuildListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Build", "State", "Builder", "Tags", "Link"]
} else {
&["Build", "State", "Builder", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Build", "State", "Builder", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
BuildState::Ok => Color::Green,
BuildState::Building => Color::DarkYellow,
BuildState::Unknown => Color::Magenta,
BuildState::Failed => Color::Red,
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.info.builder_id),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Build,
&self.id,
)));
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<RepoListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Repo", "State", "Link", "Tags", "Link"]
} else {
&["Repo", "State", "Link", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Repo", "State", "Link", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
RepoState::Ok => Color::Green,
RepoState::Building
@@ -939,34 +785,27 @@ impl PrintTable for ResourceListItem<RepoListItemInfo> {
RepoState::Unknown => Color::Magenta,
RepoState::Failed => Color::Red,
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.info.repo_link),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Repo,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<ProcedureListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Procedure", "State", "Next Run", "Tags", "Link"]
} else {
&["Procedure", "State", "Next Run", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Procedure", "State", "Next Run", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
ProcedureState::Ok => Color::Green,
ProcedureState::Running => Color::DarkYellow,
@@ -982,34 +821,27 @@ impl PrintTable for ResourceListItem<ProcedureListItemInfo> {
} else {
Cell::new(String::from("None"))
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
next_run,
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Procedure,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<ActionListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Action", "State", "Next Run", "Tags", "Link"]
} else {
&["Action", "State", "Next Run", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Action", "State", "Next Run", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
ActionState::Ok => Color::Green,
ActionState::Running => Color::DarkYellow,
@@ -1025,34 +857,27 @@ impl PrintTable for ResourceListItem<ActionListItemInfo> {
} else {
Cell::new(String::from("None"))
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
next_run,
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Action,
&self.id,
)));
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<ResourceSyncListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Sync", "State", "Tags", "Link"]
} else {
&["Sync", "State", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Sync", "State", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let color = match self.info.state {
ResourceSyncState::Ok => Color::Green,
ResourceSyncState::Pending | ResourceSyncState::Syncing => {
@@ -1061,59 +886,45 @@ impl PrintTable for ResourceListItem<ResourceSyncListItemInfo> {
ResourceSyncState::Unknown => Color::Magenta,
ResourceSyncState::Failed => Color::Red,
};
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.state.to_string())
.fg(color)
.add_attribute(Attribute::Bold),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::ResourceSync,
&self.id,
)))
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<BuilderListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Builder", "Type", "Tags", "Link"]
} else {
&["Builder", "Type", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Builder", "Type", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
let mut res = vec![
fn row(self) -> Vec<comfy_table::Cell> {
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.builder_type),
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Builder,
&self.id,
)));
}
res
)),
]
}
}
impl PrintTable for ResourceListItem<AlerterListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Alerter", "Type", "Enabled", "Tags", "Link"]
} else {
&["Alerter", "Type", "Enabled", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Alerter", "Type", "Enabled", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
let mut row = vec![
fn row(self) -> Vec<comfy_table::Cell> {
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.info.endpoint_type),
if self.info.enabled {
@@ -1122,27 +933,20 @@ impl PrintTable for ResourceListItem<AlerterListItemInfo> {
Cell::new(self.info.enabled.to_string()).fg(Color::Red)
},
Cell::new(self.tags.join(", ")),
];
if links {
row.push(Cell::new(resource_link(
Cell::new(resource_link(
&cli_config().host,
ResourceTargetVariant::Alerter,
&self.id,
)));
}
row
)),
]
}
}
impl PrintTable for Schedule {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Name", "Type", "Next Run", "Tags", "Link"]
} else {
&["Name", "Type", "Next Run", "Tags"]
}
fn header() -> &'static [&'static str] {
&["Name", "Type", "Next Run", "Tags", "Link"]
}
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
fn row(self) -> Vec<comfy_table::Cell> {
let next_run = if let Some(ts) = self.next_scheduled_run {
Cell::new(
format_timetamp(ts)
@@ -1153,19 +957,12 @@ impl PrintTable for Schedule {
Cell::new(String::from("None"))
};
let (resource_type, id) = self.target.extract_variant_id();
let mut res = vec![
vec![
Cell::new(self.name).add_attribute(Attribute::Bold),
Cell::new(self.target.extract_variant_id().0),
next_run,
Cell::new(self.tags.join(", ")),
];
if links {
res.push(Cell::new(resource_link(
&cli_config().host,
resource_type,
id,
)));
}
res
Cell::new(resource_link(&cli_config().host, resource_type, id)),
]
}
}
+11 -31
View File
@@ -5,8 +5,7 @@ use chrono::TimeZone;
use colored::Colorize;
use comfy_table::{Attribute, Cell, Table};
use komodo_client::{
KomodoClient,
entities::config::cli::{CliTableBorders, args::CliFormat},
KomodoClient, entities::config::cli::args::CliFormat,
};
use serde::Serialize;
use tokio::sync::OnceCell;
@@ -92,30 +91,19 @@ fn sanitize_uri(uri: &str) -> String {
fn print_items<T: PrintTable + Serialize>(
items: Vec<T>,
format: CliFormat,
links: bool,
) -> anyhow::Result<()> {
match format {
CliFormat::Table => {
let mut table = Table::new();
let preset = {
use comfy_table::presets::*;
match cli_config().table_borders {
None | Some(CliTableBorders::Horizontal) => {
UTF8_HORIZONTAL_ONLY
}
Some(CliTableBorders::Vertical) => UTF8_FULL_CONDENSED,
Some(CliTableBorders::Inside) => UTF8_NO_BORDERS,
Some(CliTableBorders::Outside) => UTF8_BORDERS_ONLY,
Some(CliTableBorders::All) => UTF8_FULL,
}
};
table.load_preset(preset).set_header(
T::header(links)
.into_iter()
.map(|h| Cell::new(h).add_attribute(Attribute::Bold)),
);
table
.load_preset(comfy_table::presets::UTF8_FULL)
.set_header(
T::header()
.into_iter()
.map(|h| Cell::new(h).add_attribute(Attribute::Bold)),
);
for item in items {
table.add_row(item.row(links));
table.add_row(item.row());
}
println!("{table}");
}
@@ -131,8 +119,8 @@ fn print_items<T: PrintTable + Serialize>(
}
trait PrintTable {
fn header(links: bool) -> &'static [&'static str];
fn row(self, links: bool) -> Vec<Cell>;
fn header() -> &'static [&'static str];
fn row(self) -> Vec<Cell>;
}
fn parse_wildcards(items: &[String]) -> Vec<Wildcard<'_>> {
@@ -168,14 +156,6 @@ fn format_timetamp(ts: i64) -> anyhow::Result<String> {
Ok(ts)
}
fn clamp_sha(maybe_sha: &str) -> String {
if maybe_sha.starts_with("sha256:") {
maybe_sha[0..20].to_string() + "..."
} else {
maybe_sha.to_string()
}
}
// fn text_link(link: &str, text: &str) -> String {
// format!("\x1b]8;;{link}\x07{text}\x1b]8;;\x07")
// }
-3
View File
@@ -191,9 +191,6 @@ pub fn cli_config() -> &'static CliConfig {
config_profile,
config_aliases: config.config_aliases,
default_profile: config.default_profile,
table_borders: env
.komodo_cli_table_borders
.or(config.table_borders),
host: host
.or(env.komodo_cli_host)
.or(env.komodo_host)
+1 -1
View File
@@ -1,7 +1,7 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
# Build Core
FROM rust:1.89.0-bullseye AS core-builder
FROM rust:1.88.0-bullseye AS core-builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
+3 -6
View File
@@ -25,7 +25,6 @@ use crate::{
use super::Variant;
#[derive(Default)]
pub struct AuthArgs {
pub headers: HeaderMap,
}
@@ -42,7 +41,7 @@ pub struct AuthArgs {
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
pub enum AuthRequest {
GetLoginOptions(GetLoginOptions),
SignUpLocalUser(SignUpLocalUser),
CreateLocalUser(CreateLocalUser),
LoginLocalUser(LoginLocalUser),
ExchangeForJwt(ExchangeForJwt),
GetUser(GetUser),
@@ -139,10 +138,8 @@ impl Resolve<AuthArgs> for ExchangeForJwt {
self,
_: &AuthArgs,
) -> serror::Result<ExchangeForJwtResponse> {
jwt_client()
.redeem_exchange_token(&self.token)
.await
.map_err(Into::into)
let jwt = jwt_client().redeem_exchange_token(&self.token).await?;
Ok(ExchangeForJwtResponse { jwt })
}
}
-319
View File
@@ -1,319 +0,0 @@
use std::sync::OnceLock;
use anyhow::{Context, anyhow};
use command::run_komodo_command;
use database::mungos::{find::find_collect, mongodb::bson::doc};
use formatting::{bold, format_serror};
use komodo_client::{
api::execute::{
BackupCoreDatabase, ClearRepoCache, GlobalAutoUpdate,
},
entities::{
deployment::DeploymentState, server::ServerState,
stack::StackState,
},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use tokio::sync::Mutex;
use crate::{
api::execute::{
ExecuteArgs, pull_deployment_inner, pull_stack_inner,
},
config::core_config,
helpers::update::update_update,
state::{
db_client, deployment_status_cache, server_status_cache,
stack_status_cache,
},
};
/// Makes sure the method can only be called once at a time
fn clear_repo_cache_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(Default::default)
}
impl Resolve<ExecuteArgs> for ClearRepoCache {
#[instrument(
name = "ClearRepoCache",
skip(user, update),
fields(user_id = user.id, update_id = update.id)
)]
async fn resolve(
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
anyhow!("This method is admin only.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
let _lock = clear_repo_cache_lock()
.try_lock()
.context("Clear already in progress...")?;
let mut update = update.clone();
let mut contents =
tokio::fs::read_dir(&core_config().repo_directory)
.await
.context("Failed to read repo cache directory")?;
loop {
let path = match contents
.next_entry()
.await
.context("Failed to read contents at path")
{
Ok(Some(contents)) => contents.path(),
Ok(None) => break,
Err(e) => {
update.push_error_log(
"Read Directory",
format_serror(&e.into()),
);
continue;
}
};
if path.is_dir() {
match tokio::fs::remove_dir_all(&path)
.await
.context("Failed to clear contents at path")
{
Ok(_) => {}
Err(e) => {
update.push_error_log(
"Clear Directory",
format_serror(&e.into()),
);
}
};
}
}
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
//
/// Makes sure the method can only be called once at a time
fn backup_database_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(Default::default)
}
impl Resolve<ExecuteArgs> for BackupCoreDatabase {
#[instrument(
name = "BackupCoreDatabase",
skip(user, update),
fields(user_id = user.id, update_id = update.id)
)]
async fn resolve(
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
anyhow!("This method is admin only.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
let _lock = backup_database_lock()
.try_lock()
.context("Backup already in progress...")?;
let mut update = update.clone();
update_update(update.clone()).await?;
let res = run_komodo_command(
"Backup Core Database",
None,
"km database backup --yes",
)
.await;
update.logs.push(res);
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
//
/// Makes sure the method can only be called once at a time
fn global_update_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(Default::default)
}
impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
#[instrument(
name = "GlobalAutoUpdate",
skip(user, update),
fields(user_id = user.id, update_id = update.id)
)]
async fn resolve(
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
anyhow!("This method is admin only.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
let _lock = global_update_lock()
.try_lock()
.context("Global update already in progress...")?;
let mut update = update.clone();
update_update(update.clone()).await?;
// This is all done in sequence because there is no rush,
// the pulls / deploys happen spaced out to ease the load on system.
let servers = find_collect(&db_client().servers, None, None)
.await
.context("Failed to query for servers from database")?;
let query = doc! {
"$or": [
{ "config.poll_for_updates": true },
{ "config.auto_update": true }
]
};
let (stacks, repos) = tokio::try_join!(
find_collect(&db_client().stacks, query.clone(), None),
find_collect(&db_client().repos, None, None)
)
.context("Failed to query for resources from database")?;
let server_status_cache = server_status_cache();
let stack_status_cache = stack_status_cache();
// Will be edited later at update.logs[0]
update.push_simple_log("Auto Pull", String::new());
for stack in stacks {
let Some(status) = stack_status_cache.get(&stack.id).await
else {
continue;
};
// Only pull running stacks.
if !matches!(status.curr.state, StackState::Running) {
continue;
}
if let Some(server) =
servers.iter().find(|s| s.id == stack.config.server_id)
// This check is probably redundant along with running check
// but shouldn't hurt
&& server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
let name = stack.name.clone();
let repo = if stack.config.linked_repo.is_empty() {
None
} else {
let Some(repo) =
repos.iter().find(|r| r.id == stack.config.linked_repo)
else {
update.push_error_log(
&format!("Pull Stack {name}"),
format!(
"Did not find any Repo matching {}",
stack.config.linked_repo
),
);
continue;
};
Some(repo.clone())
};
if let Err(e) =
pull_stack_inner(stack, Vec::new(), server, repo, None)
.await
{
update.push_error_log(
&format!("Pull Stack {name}"),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
}
update.logs[0]
.stdout
.push_str(&format!("Pulled Stack {} ✅", bold(name)));
}
}
}
let deployment_status_cache = deployment_status_cache();
let deployments =
find_collect(&db_client().deployments, query, None)
.await
.context("Failed to query for deployments from database")?;
for deployment in deployments {
let Some(status) =
deployment_status_cache.get(&deployment.id).await
else {
continue;
};
// Only pull running deployments.
if !matches!(status.curr.state, DeploymentState::Running) {
continue;
}
if let Some(server) =
servers.iter().find(|s| s.id == deployment.config.server_id)
// This check is probably redundant along with running check
// but shouldn't hurt
&& server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
let name = deployment.name.clone();
if let Err(e) =
pull_deployment_inner(deployment, server).await
{
update.push_error_log(
&format!("Pull Deployment {name}"),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
}
update.logs[0].stdout.push_str(&format!(
"Pulled Deployment {} ✅",
bold(name)
));
}
}
}
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
+74 -6
View File
@@ -1,6 +1,6 @@
use std::{pin::Pin, time::Instant};
use std::{pin::Pin, sync::OnceLock, time::Instant};
use anyhow::Context;
use anyhow::{Context, anyhow};
use axum::{
Extension, Router, extract::Path, middleware, routing::post,
};
@@ -18,16 +18,19 @@ use komodo_client::{
user::User,
},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use response::JsonString;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::Json;
use serror::{AddStatusCodeError, Json};
use tokio::sync::Mutex;
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::auth_request,
config::core_config,
helpers::update::{init_execution_update, update_update},
resource::{KomodoResource, list_full_for_user_using_pattern},
state::db_client,
@@ -37,7 +40,6 @@ mod action;
mod alerter;
mod build;
mod deployment;
mod maintenance;
mod procedure;
mod repo;
mod server;
@@ -145,8 +147,6 @@ pub enum ExecuteRequest {
// ==== MAINTENANCE ====
ClearRepoCache(ClearRepoCache),
BackupCoreDatabase(BackupCoreDatabase),
GlobalAutoUpdate(GlobalAutoUpdate),
}
pub fn router() -> Router {
@@ -334,3 +334,71 @@ async fn batch_execute<E: BatchExecute>(
});
Ok(join_all(futures).await)
}
fn clear_repo_cache_lock() -> &'static Mutex<()> {
static CLEAR_REPO_CACHE_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
CLEAR_REPO_CACHE_LOCK.get_or_init(Default::default)
}
impl Resolve<ExecuteArgs> for ClearRepoCache {
#[instrument(name = "ClearRepoCache", skip(user, update), fields(user_id = user.id, update_id = update.id))]
async fn resolve(
self,
ExecuteArgs { user, update }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
anyhow!("This method is admin only.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
let _lock = clear_repo_cache_lock()
.try_lock()
.context("Clear already in progress...")?;
let mut update = update.clone();
let mut contents =
tokio::fs::read_dir(&core_config().repo_directory)
.await
.context("Failed to read repo cache directory")?;
loop {
let path = match contents
.next_entry()
.await
.context("Failed to read contents at path")
{
Ok(Some(contents)) => contents.path(),
Ok(None) => break,
Err(e) => {
update.push_error_log(
"Read Directory",
format_serror(&e.into()),
);
continue;
}
};
if path.is_dir() {
match tokio::fs::remove_dir_all(&path)
.await
.context("Failed to clear contents at path")
{
Ok(_) => {}
Err(e) => {
update.push_error_log(
"Clear Directory",
format_serror(&e.into()),
);
}
};
}
}
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
-1
View File
@@ -52,7 +52,6 @@ pub struct WriteArgs {
#[serde(tag = "type", content = "params")]
pub enum WriteRequest {
// ==== USER ====
CreateLocalUser(CreateLocalUser),
UpdateUserUsername(UpdateUserUsername),
UpdateUserPassword(UpdateUserPassword),
DeleteUser(DeleteUser),
+19
View File
@@ -27,6 +27,7 @@ use periphery_client::api::compose::{
use resolver_api::Resolve;
use crate::{
api::execute::pull_stack_inner,
config::core_config,
helpers::{
periphery_client,
@@ -555,6 +556,24 @@ impl Resolve<WriteArgs> for RefreshStackCache {
.await
.context("failed to update stack info on db")?;
if (stack.config.poll_for_updates || stack.config.auto_update)
&& !stack.config.server_id.is_empty()
{
let (server, state) =
get_server_with_state(&stack.config.server_id).await?;
if state == ServerState::Ok {
let name = stack.name.clone();
if let Err(e) =
pull_stack_inner(stack, Vec::new(), &server, repo, None)
.await
{
warn!(
"Failed to pull latest images for Stack {name} | {e:#}",
);
}
}
}
Ok(NoData {})
}
}
+13 -4
View File
@@ -8,9 +8,17 @@ use database::mungos::{
use komodo_client::{
api::write::{CreateTag, DeleteTag, RenameTag, UpdateTagColor},
entities::{
action::Action, alerter::Alerter, build::Build, builder::Builder,
deployment::Deployment, procedure::Procedure, repo::Repo,
server::Server, stack::Stack, sync::ResourceSync, tag::Tag,
action::Action,
alerter::Alerter,
build::Build,
builder::Builder,
deployment::Deployment,
procedure::Procedure,
repo::Repo,
server::Server,
stack::Stack,
sync::ResourceSync,
tag::{Tag, TagColor},
},
};
use resolver_api::Resolve;
@@ -36,8 +44,9 @@ impl Resolve<WriteArgs> for CreateTag {
let mut tag = Tag {
id: Default::default(),
name: self.name,
color: self.color.unwrap_or_default(),
color: TagColor::Slate,
owner: user.id.clone(),
unused: false,
};
tag.id = db_client()
+7 -102
View File
@@ -1,21 +1,16 @@
use std::str::FromStr;
use anyhow::{Context, anyhow};
use async_timing_util::unix_timestamp_ms;
use database::{
hash_password,
mungos::mongodb::bson::{doc, oid::ObjectId},
};
use database::mungos::mongodb::bson::{doc, oid::ObjectId};
use komodo_client::{
api::write::*,
entities::{
NoData,
user::{User, UserConfig},
api::write::{
DeleteUser, DeleteUserResponse, UpdateUserPassword,
UpdateUserPasswordResponse, UpdateUserUsername,
UpdateUserUsernameResponse,
},
entities::NoData,
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{config::core_config, state::db_client};
@@ -23,85 +18,7 @@ use super::WriteArgs;
//
impl Resolve<WriteArgs> for CreateLocalUser {
#[instrument(name = "CreateLocalUser", skip(admin, self), fields(admin_id = admin.id, username = self.username))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<CreateLocalUserResponse> {
if !admin.admin {
return Err(
anyhow!("This method is admin-only.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
if self.username.is_empty() {
return Err(anyhow!("Username cannot be empty.").into());
}
if ObjectId::from_str(&self.username).is_ok() {
return Err(
anyhow!("Username cannot be valid ObjectId").into(),
);
}
if self.password.is_empty() {
return Err(anyhow!("Password cannot be empty.").into());
}
let db = db_client();
if db
.users
.find_one(doc! { "username": &self.username })
.await
.context("Failed to query for existing users")?
.is_some()
{
return Err(anyhow!("Username already taken.").into());
}
let ts = unix_timestamp_ms() as i64;
let hashed_password = hash_password(self.password)?;
let mut user = User {
id: Default::default(),
username: self.username,
enabled: true,
admin: false,
super_admin: false,
create_server_permissions: false,
create_build_permissions: false,
updated_at: ts,
last_update_view: 0,
recents: Default::default(),
all: Default::default(),
config: UserConfig::Local {
password: hashed_password,
},
};
user.id = db_client()
.users
.insert_one(&user)
.await
.context("failed to create user")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
user.sanitize();
Ok(user)
}
}
//
impl Resolve<WriteArgs> for UpdateUserUsername {
#[instrument(name = "UpdateUserUsername", skip(user), fields(user_id = user.id))]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
@@ -119,13 +36,6 @@ impl Resolve<WriteArgs> for UpdateUserUsername {
if self.username.is_empty() {
return Err(anyhow!("Username cannot be empty.").into());
}
if ObjectId::from_str(&self.username).is_ok() {
return Err(
anyhow!("Username cannot be valid ObjectId").into(),
);
}
let db = db_client();
if db
.users
@@ -152,7 +62,6 @@ impl Resolve<WriteArgs> for UpdateUserUsername {
//
impl Resolve<WriteArgs> for UpdateUserPassword {
#[instrument(name = "UpdateUserPassword", skip(user, self), fields(user_id = user.id))]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
@@ -175,16 +84,12 @@ impl Resolve<WriteArgs> for UpdateUserPassword {
//
impl Resolve<WriteArgs> for DeleteUser {
#[instrument(name = "DeleteUser", skip(admin), fields(user = self.user))]
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<DeleteUserResponse> {
if !admin.admin {
return Err(
anyhow!("This method is admin-only.")
.status_code(StatusCode::UNAUTHORIZED),
);
return Err(anyhow!("Calling user is not admin.").into());
}
if admin.username == self.user || admin.id == self.user {
return Err(anyhow!("User cannot delete themselves.").into());
+8 -17
View File
@@ -8,15 +8,13 @@ use database::mungos::mongodb::bson::doc;
use jsonwebtoken::{
DecodingKey, EncodingKey, Header, Validation, decode, encode,
};
use komodo_client::{
api::auth::JwtResponse, entities::config::core::CoreConfig,
};
use komodo_client::entities::config::core::CoreConfig;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use crate::helpers::random_string;
type ExchangeTokenMap = Mutex<HashMap<String, (JwtResponse, u128)>>;
type ExchangeTokenMap = Mutex<HashMap<String, (String, u128)>>;
#[derive(Serialize, Deserialize)]
pub struct JwtClaims {
@@ -53,20 +51,16 @@ impl JwtClient {
})
}
pub fn encode(
&self,
user_id: String,
) -> anyhow::Result<JwtResponse> {
pub fn encode(&self, user_id: String) -> anyhow::Result<String> {
let iat = unix_timestamp_ms();
let exp = iat + self.ttl_ms;
let claims = JwtClaims {
id: user_id.clone(),
id: user_id,
iat,
exp,
};
let jwt = encode(&self.header, &claims, &self.encoding_key)
.context("failed at signing claim")?;
Ok(JwtResponse { user_id, jwt })
encode(&self.header, &claims, &self.encoding_key)
.context("failed at signing claim")
}
pub fn decode(&self, jwt: &str) -> anyhow::Result<JwtClaims> {
@@ -76,10 +70,7 @@ impl JwtClient {
}
#[instrument(level = "debug", skip_all)]
pub async fn create_exchange_token(
&self,
jwt: JwtResponse,
) -> String {
pub async fn create_exchange_token(&self, jwt: String) -> String {
let exchange_token = random_string(40);
self.exchange_tokens.lock().await.insert(
exchange_token.clone(),
@@ -95,7 +86,7 @@ impl JwtClient {
pub async fn redeem_exchange_token(
&self,
exchange_token: &str,
) -> anyhow::Result<JwtResponse> {
) -> anyhow::Result<String> {
let (jwt, valid_until) = self
.exchange_tokens
.lock()
+17 -26
View File
@@ -8,8 +8,8 @@ use database::{
};
use komodo_client::{
api::auth::{
LoginLocalUser, LoginLocalUserResponse, SignUpLocalUser,
SignUpLocalUserResponse,
CreateLocalUser, CreateLocalUserResponse, LoginLocalUser,
LoginLocalUserResponse,
},
entities::user::{User, UserConfig},
};
@@ -21,12 +21,12 @@ use crate::{
state::{db_client, jwt_client},
};
impl Resolve<AuthArgs> for SignUpLocalUser {
#[instrument(name = "SignUpLocalUser", skip(self))]
impl Resolve<AuthArgs> for CreateLocalUser {
#[instrument(name = "CreateLocalUser", skip(self))]
async fn resolve(
self,
_: &AuthArgs,
) -> serror::Result<SignUpLocalUserResponse> {
) -> serror::Result<CreateLocalUserResponse> {
let core_config = core_config();
if !core_config.local_auth {
@@ -47,27 +47,16 @@ impl Resolve<AuthArgs> for SignUpLocalUser {
return Err(anyhow!("Password cannot be empty string").into());
}
let db = db_client();
let hashed_password = hash_password(self.password)?;
let no_users_exist =
db.users.find_one(Document::new()).await?.is_none();
db_client().users.find_one(Document::new()).await?.is_none();
if !no_users_exist && core_config.disable_user_registration {
return Err(anyhow!("User registration is disabled").into());
}
if db
.users
.find_one(doc! { "username": &self.username })
.await
.context("Failed to query for existing users")?
.is_some()
{
return Err(anyhow!("Username already taken.").into());
}
let ts = unix_timestamp_ms() as i64;
let hashed_password = hash_password(self.password)?;
let user = User {
id: Default::default(),
@@ -96,10 +85,11 @@ impl Resolve<AuthArgs> for SignUpLocalUser {
.context("inserted_id is not ObjectId")?
.to_string();
jwt_client()
.encode(user_id.clone())
.context("failed to generate jwt for user")
.map_err(Into::into)
let jwt = jwt_client()
.encode(user_id)
.context("failed to generate jwt for user")?;
Ok(CreateLocalUserResponse { jwt })
}
}
@@ -141,9 +131,10 @@ impl Resolve<AuthArgs> for LoginLocalUser {
return Err(anyhow!("invalid credentials").into());
}
jwt_client()
.encode(user.id.clone())
.context("failed at generating jwt for user")
.map_err(Into::into)
let jwt = jwt_client()
.encode(user.id)
.context("failed at generating jwt for user")?;
Ok(LoginLocalUserResponse { jwt })
}
}
+1 -12
View File
@@ -109,14 +109,6 @@ pub fn core_config() -> &'static CoreConfig {
.komodo_database_db_name
.unwrap_or(config.database.db_name),
},
init_admin_username: maybe_read_item_from_file(
env.komodo_init_admin_username_file,
env.komodo_init_admin_username
).or(config.init_admin_username),
init_admin_password: maybe_read_item_from_file(
env.komodo_init_admin_password_file,
env.komodo_init_admin_password
).unwrap_or(config.init_admin_password),
oidc_enabled: env.komodo_oidc_enabled.unwrap_or(config.oidc_enabled),
oidc_provider: env.komodo_oidc_provider.unwrap_or(config.oidc_provider),
oidc_redirect_host: env.komodo_oidc_redirect_host.unwrap_or(config.oidc_redirect_host),
@@ -177,8 +169,7 @@ pub fn core_config() -> &'static CoreConfig {
port: env.komodo_port.unwrap_or(config.port),
bind_ip: env.komodo_bind_ip.unwrap_or(config.bind_ip),
timezone: env.komodo_timezone.unwrap_or(config.timezone),
first_server: env.komodo_first_server.or(config.first_server),
first_server_name: env.komodo_first_server_name.unwrap_or(config.first_server_name),
first_server: env.komodo_first_server.unwrap_or(config.first_server),
frontend_path: env.komodo_frontend_path.unwrap_or(config.frontend_path),
jwt_ttl: env
.komodo_jwt_ttl
@@ -223,8 +214,6 @@ pub fn core_config() -> &'static CoreConfig {
.unwrap_or(config.disable_user_registration),
disable_non_admin_create: env.komodo_disable_non_admin_create
.unwrap_or(config.disable_non_admin_create),
disable_init_resources: env.komodo_disable_init_resources
.unwrap_or(config.disable_init_resources),
enable_fancy_toml: env.komodo_enable_fancy_toml
.unwrap_or(config.enable_fancy_toml),
lock_login_credentials_for: env.komodo_lock_login_credentials_for
+1 -35
View File
@@ -1136,41 +1136,7 @@ async fn execute_execution(
.resolve(&ExecuteArgs { user, update })
.await
.map_err(|e| e.error)
.context("Failed at ClearRepoCache"),
&update_id,
)
.await?
}
Execution::BackupCoreDatabase(req) => {
let req = ExecuteRequest::BackupCoreDatabase(req);
let update = init_execution_update(&req, &user).await?;
let ExecuteRequest::BackupCoreDatabase(req) = req else {
unreachable!()
};
let update_id = update.id.clone();
handle_resolve_result(
req
.resolve(&ExecuteArgs { user, update })
.await
.map_err(|e| e.error)
.context("Failed at BackupCoreDatabase"),
&update_id,
)
.await?
}
Execution::GlobalAutoUpdate(req) => {
let req = ExecuteRequest::GlobalAutoUpdate(req);
let update = init_execution_update(&req, &user).await?;
let ExecuteRequest::GlobalAutoUpdate(req) = req else {
unreachable!()
};
let update_id = update.id.clone();
handle_resolve_result(
req
.resolve(&ExecuteArgs { user, update })
.await
.map_err(|e| e.error)
.context("Failed at GlobalAutoUpdate"),
.context("Failed at TestAlerter"),
&update_id,
)
.await?
+18 -20
View File
@@ -3,7 +3,7 @@ use async_timing_util::{
ONE_DAY_MS, Timelength, unix_timestamp_ms, wait_until_timelength,
};
use database::mungos::{find::find_collect, mongodb::bson::doc};
use futures::{StreamExt, stream::FuturesUnordered};
use futures::future::join_all;
use periphery_client::api::image::PruneImages;
use crate::{config::core_config, state::db_client};
@@ -30,26 +30,24 @@ pub fn spawn_prune_loop() {
}
async fn prune_images() -> anyhow::Result<()> {
let mut futures = find_collect(
&db_client().servers,
doc! { "config.enabled": true, "config.auto_prune": true },
None,
)
.await
.context("failed to get servers from db")?
.into_iter()
.map(|server| async move {
(
async {
periphery_client(&server)?.request(PruneImages {}).await
}
.await,
server,
)
})
.collect::<FuturesUnordered<_>>();
let futures = find_collect(&db_client().servers, None, None)
.await
.context("failed to get servers from db")?
.into_iter()
.filter(|server| {
server.config.enabled && server.config.auto_prune
})
.map(|server| async move {
(
async {
periphery_client(&server)?.request(PruneImages {}).await
}
.await,
server,
)
});
while let Some((res, server)) = futures.next().await {
for (res, server) in join_all(futures).await {
if let Err(e) = res {
error!(
"failed to prune images on server {} ({}) | {e:#}",
-6
View File
@@ -504,12 +504,6 @@ pub async fn init_execution_update(
ExecuteRequest::ClearRepoCache(_data) => {
(Operation::ClearRepoCache, ResourceTarget::system())
}
ExecuteRequest::BackupCoreDatabase(_data) => {
(Operation::BackupCoreDatabase, ResourceTarget::system())
}
ExecuteRequest::GlobalAutoUpdate(_data) => {
(Operation::GlobalAutoUpdate, ResourceTarget::system())
}
};
let mut update = make_update(target, operation, user);
+1 -1
View File
@@ -65,7 +65,7 @@ async fn app() -> anyhow::Result<()> {
// Spawn background tasks
monitor::spawn_monitor_loop();
resource::spawn_resource_refresh_loop();
resource::spawn_all_resources_cache_refresh_loop();
resource::spawn_all_resources_refresh_loop();
resource::spawn_build_state_refresh_loop();
resource::spawn_repo_state_refresh_loop();
resource::spawn_procedure_state_refresh_loop();
+2 -19
View File
@@ -7,10 +7,8 @@ use komodo_client::entities::{
};
use crate::{
alert::send_alerts,
monitor::deployment_status_cache,
resource,
state::{action_states, db_client},
alert::send_alerts, monitor::deployment_status_cache, resource,
state::db_client,
};
#[instrument(level = "debug")]
@@ -19,7 +17,6 @@ pub async fn alert_deployments(
server_names: &HashMap<String, String>,
) {
let mut alerts = Vec::<Alert>::new();
let action_states = action_states();
for status in deployment_status_cache().get_list().await {
// Don't alert if prev None
let Some(prev) = status.prev else {
@@ -34,20 +31,6 @@ pub async fn alert_deployments(
continue;
}
// Don't alert if deploying
if action_states
.deployment
.get(&status.curr.id)
.await
.map(|s| s.get().map(|s| s.deploying))
.transpose()
.ok()
.flatten()
.unwrap_or_default()
{
continue;
}
if status.curr.state != prev {
// send alert
let Ok(deployment) =
+1 -16
View File
@@ -9,7 +9,7 @@ use komodo_client::entities::{
use crate::{
alert::send_alerts,
resource,
state::{action_states, db_client, stack_status_cache},
state::{db_client, stack_status_cache},
};
#[instrument(level = "debug")]
@@ -17,7 +17,6 @@ pub async fn alert_stacks(
ts: i64,
server_names: &HashMap<String, String>,
) {
let action_states = action_states();
let mut alerts = Vec::<Alert>::new();
for status in stack_status_cache().get_list().await {
// Don't alert if prev None
@@ -33,20 +32,6 @@ pub async fn alert_stacks(
continue;
}
// Don't alert if deploying
if action_states
.stack
.get(&status.curr.id)
.await
.map(|s| s.get().map(|s| s.deploying))
.transpose()
.ok()
.flatten()
.unwrap_or_default()
{
continue;
}
if status.curr.state != prev {
// send alert
let Ok(stack) =
+1 -3
View File
@@ -70,8 +70,7 @@ pub use procedure::{
refresh_procedure_state_cache, spawn_procedure_state_refresh_loop,
};
pub use refresh::{
refresh_all_resources_cache,
spawn_all_resources_cache_refresh_loop,
refresh_all_resources_cache, spawn_all_resources_refresh_loop,
spawn_resource_refresh_loop,
};
pub use repo::{
@@ -707,7 +706,6 @@ pub async fn update_meta<T: KomodoResource>(
Ok(tag) => Ok(tag.id),
Err(_) => CreateTag {
name: tag.to_string(),
color: None,
}
.resolve(args)
.await
-14
View File
@@ -732,20 +732,6 @@ async fn validate_config(
));
}
}
Execution::BackupCoreDatabase(_params) => {
if !user.admin {
return Err(anyhow!(
"Non admin user cannot trigger core database backup"
));
}
}
Execution::GlobalAutoUpdate(_params) => {
if !user.admin {
return Err(anyhow!(
"Non admin user cannot trigger global auto update"
));
}
}
Execution::Sleep(_) => {}
}
}
+39 -2
View File
@@ -12,13 +12,13 @@ use komodo_client::{
use resolver_api::Resolve;
use crate::{
api::write::WriteArgs,
api::{execute::pull_deployment_inner, write::WriteArgs},
config::core_config,
helpers::all_resources::AllResourcesById,
state::{all_resources_cache, db_client},
};
pub fn spawn_all_resources_cache_refresh_loop() {
pub fn spawn_all_resources_refresh_loop() {
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(15));
loop {
@@ -57,6 +57,7 @@ pub fn spawn_resource_refresh_loop() {
async fn refresh_all() {
refresh_stacks().await;
refresh_deployments().await;
refresh_builds().await;
refresh_repos().await;
refresh_syncs().await;
@@ -86,6 +87,42 @@ async fn refresh_stacks() {
}
}
async fn refresh_deployments() {
let servers = find_collect(&db_client().servers, None, None)
.await
.inspect_err(|e| {
warn!(
"Failed to get Servers from database in refresh task | {e:#}"
)
})
.unwrap_or_default();
let Ok(deployments) = find_collect(&db_client().deployments, None, None)
.await
.inspect_err(|e| {
warn!(
"Failed to get Deployments from database in refresh task | {e:#}"
)
})
else {
return;
};
for deployment in deployments {
if (deployment.config.poll_for_updates
|| deployment.config.auto_update)
&& let Some(server) =
servers.iter().find(|s| s.id == deployment.config.server_id)
{
let name = deployment.name.clone();
if let Err(e) = pull_deployment_inner(deployment, server).await
{
warn!(
"Failed to pull latest image for Deployment {name} | {e:#}"
);
}
}
}
}
async fn refresh_builds() {
let Ok(builds) = find_collect(&db_client().builds, None, None)
.await
-1
View File
@@ -75,7 +75,6 @@ impl super::KomodoResource for Server {
.unwrap_or(String::from("Unknown")),
region: server.config.region,
address: server.config.address,
external_address: server.config.external_address,
send_unreachable_alerts: server
.config
.send_unreachable_alerts,
+40 -208
View File
@@ -1,30 +1,20 @@
use std::str::FromStr;
use colored::Colorize;
use database::mungos::{
find::find_collect,
mongodb::bson::{Document, doc, oid::ObjectId, to_document},
};
use futures::future::join_all;
use komodo_client::{
api::{
auth::SignUpLocalUser,
execute::{
BackupCoreDatabase, Execution, GlobalAutoUpdate, RunAction,
},
write::{
CreateBuilder, CreateProcedure, CreateServer, CreateTag,
UpdateResourceMeta,
},
},
api::execute::RunAction,
api::write::{CreateBuilder, CreateServer},
entities::{
ResourceTarget,
action::Action,
builder::{PartialBuilderConfig, PartialServerBuilderConfig},
komodo_timestamp,
procedure::{EnabledExecution, ProcedureConfig, ProcedureStage},
server::{PartialServerConfig, Server},
sync::ResourceSync,
tag::TagColor,
update::Log,
user::{action_user, system_user},
},
@@ -32,32 +22,44 @@ use komodo_client::{
use resolver_api::Resolve;
use crate::{
api::{
auth::AuthArgs,
execute::{ExecuteArgs, ExecuteRequest},
write::WriteArgs,
},
api::execute::{ExecuteArgs, ExecuteRequest},
api::write::WriteArgs,
config::core_config,
helpers::random_string,
helpers::update::init_execution_update,
network, resource,
state::db_client,
};
/// Runs the Actions with `run_at_startup: true`
/// This function should be run on startup,
/// after the db client has been initialized
pub async fn on_startup() {
// Configure manual network interface if specified
network::configure_internet_gateway().await;
tokio::join!(
in_progress_update_cleanup(),
open_alert_cleanup(),
ensure_first_server_and_builder(),
clean_up_server_templates(),
);
}
pub async fn run_startup_actions() {
let startup_actions = match find_collect(
&db_client().actions,
doc! { "config.run_at_startup": true },
None,
)
.await
{
Ok(actions) => actions,
Err(e) => {
error!("Failed to fetch actions for startup | {e:#?}");
return;
}
};
let startup_actions =
match database::mungos::find::find_collect::<Action>(
&db_client().actions,
doc! { "config.run_at_startup": true },
None,
)
.await
{
Ok(actions) => actions,
Err(e) => {
error!("Failed to fetch actions for startup | {e:#?}");
return;
}
};
for action in startup_actions {
let name = action.name;
@@ -97,21 +99,6 @@ pub async fn run_startup_actions() {
}
}
/// This function should be run on startup,
/// after the db client has been initialized
pub async fn on_startup() {
// Configure manual network interface if specified
network::configure_internet_gateway().await;
tokio::join!(
in_progress_update_cleanup(),
open_alert_cleanup(),
clean_up_server_templates(),
ensure_first_server_and_builder(),
ensure_init_user_and_resources(),
);
}
async fn in_progress_update_cleanup() {
let log = Log::error(
"Komodo shutdown",
@@ -197,10 +184,10 @@ async fn open_alert_cleanup() {
/// Ensures a default server / builder exists with the defined address
async fn ensure_first_server_and_builder() {
let config = core_config();
let Some(address) = config.first_server.clone() else {
let first_server = &core_config().first_server;
if first_server.is_empty() {
return;
};
}
let db = db_client();
let Ok(server) = db
.servers
@@ -214,9 +201,9 @@ async fn ensure_first_server_and_builder() {
server
} else {
match (CreateServer {
name: config.first_server_name.clone(),
name: format!("server-{}", random_string(5)),
config: PartialServerConfig {
address: Some(address),
address: Some(first_server.to_string()),
enabled: Some(true),
..Default::default()
},
@@ -242,7 +229,7 @@ async fn ensure_first_server_and_builder() {
return;
};
if let Err(e) = (CreateBuilder {
name: String::from("Local"),
name: String::from("local"),
config: PartialBuilderConfig::Server(
PartialServerBuilderConfig {
server_id: Some(server.id),
@@ -261,161 +248,6 @@ async fn ensure_first_server_and_builder() {
}
}
async fn ensure_init_user_and_resources() {
let db = db_client();
// Assumes if there are any existing users, procedures, or tags,
// the default procedures do not need to be set up.
let Ok((None, None, None)) = tokio::try_join!(
db.users.find_one(Document::new()),
db.procedures.find_one(Document::new()),
db.tags.find_one(Document::new()),
).inspect_err(|e| error!("Failed to initialize default procedures | Failed to query db | {e:?}")) else {
return
};
let config = core_config();
// Init admin user if set in config.
if let Some(username) = &config.init_admin_username {
info!("Creating init admin user...");
SignUpLocalUser {
username: username.clone(),
password: config.init_admin_password.clone(),
}
.resolve(&AuthArgs::default())
.await
.expect("Failed to initialize default admin user.")
.jwt;
db.users
.find_one(doc! { "username": username })
.await
.expect("Failed to query database for initial user")
.expect("Failed to find initial user after creation");
};
if config.disable_init_resources {
info!("System resources init {}", "DISABLED".red());
return;
}
info!("Creating init system resources...");
let write_args = WriteArgs {
user: system_user().to_owned(),
};
// Create default 'system' tag
let default_tags = match (CreateTag {
name: String::from("system"),
color: Some(TagColor::Red),
})
.resolve(&write_args)
.await
{
Ok(tag) => vec![tag.id],
Err(e) => {
warn!("Failed to create default tag | {:#}", e.error);
Vec::new()
}
};
// Backup Core Database
async {
let Ok(config) = ProcedureConfig::builder()
.stages(vec![ProcedureStage {
name: String::from("Stage 1"),
enabled: true,
executions: vec![
EnabledExecution {
execution: Execution::BackupCoreDatabase(BackupCoreDatabase {}),
enabled: true
}
]
}])
.schedule(String::from("Every day at 01:00"))
.build()
.inspect_err(|e| error!("Failed to initialize backup core database procedure | Failed to build Procedure | {e:?}")) else {
return;
};
let procedure = match (CreateProcedure {
name: String::from("Backup Core Database"),
config: config.into()
}).resolve(&write_args).await {
Ok(procedure) => procedure,
Err(e) => {
error!(
"Failed to initialize default database backup Procedure | Failed to create Procedure | {:#}",
e.error
);
return;
}
};
if let Err(e) = (UpdateResourceMeta {
target: ResourceTarget::Procedure(procedure.id),
tags: Some(default_tags.clone()),
description: Some(String::from(
"Triggers the Core database backup at the scheduled time.",
)),
template: None,
}).resolve(&write_args).await {
warn!("Failed to update default database backup Procedure tags / description | {:#}", e.error);
}
}.await;
// GlobalAutoUpdate
async {
let Ok(config) = ProcedureConfig::builder()
.stages(vec![ProcedureStage {
name: String::from("Stage 1"),
enabled: true,
executions: vec![
EnabledExecution {
execution: Execution::GlobalAutoUpdate(GlobalAutoUpdate {}),
enabled: true
}
]
}])
.schedule(String::from("Every day at 03:00"))
.build()
.inspect_err(|e| error!("Failed to initialize global auto update procedure | Failed to build Procedure | {e:?}")) else {
return;
};
let procedure = match (CreateProcedure {
name: String::from("Global Auto Update"),
config: config.into(),
})
.resolve(&write_args)
.await
{
Ok(procedure) => procedure,
Err(e) => {
error!(
"Failed to initialize global auto update Procedure | Failed to create Procedure | {:#}",
e.error
);
return;
}
};
if let Err(e) = (UpdateResourceMeta {
target: ResourceTarget::Procedure(procedure.id),
tags: Some(default_tags.clone()),
description: Some(String::from(
"Pulls and auto updates Stacks and Deployments using 'poll_for_updates' or 'auto_update'.",
)),
template: None,
})
.resolve(&write_args)
.await
{
warn!(
"Failed to update global auto update Procedure tags / description | {:#}",
e.error
);
}
}.await;
}
/// v1.17.5 removes the ServerTemplate resource.
/// References to this resource type need to be cleaned up
/// to avoid type errors reading from the database.
-2
View File
@@ -134,13 +134,11 @@ pub fn action_states() -> &'static ActionStates {
ACTION_STATES.get_or_init(ActionStates::default)
}
/// Cache of ids to status
pub type DeploymentStatusCache = Cache<
String,
Arc<History<CachedDeploymentStatus, DeploymentState>>,
>;
/// Cache of ids to status
pub fn deployment_status_cache() -> &'static DeploymentStatusCache {
static DEPLOYMENT_STATUS_CACHE: OnceLock<DeploymentStatusCache> =
OnceLock::new();
-2
View File
@@ -670,8 +670,6 @@ impl ResourceSyncTrait for Procedure {
.unwrap_or_default();
}
Execution::ClearRepoCache(_) => {}
Execution::BackupCoreDatabase(_) => {}
Execution::GlobalAutoUpdate(_) => {}
Execution::Sleep(_) => {}
}
}
+3 -12
View File
@@ -28,8 +28,6 @@ pub const TOML_PRETTY_OPTIONS: toml_pretty::Options =
toml_pretty::Options {
tab: " ",
skip_empty_string: true,
// Usually we do this, but has to be changed for some cases.
skip_empty_object: true,
max_inline_array_length: 30,
inline_array: false,
};
@@ -790,9 +788,7 @@ impl ToToml for Procedure {
),
Execution::None(_)
| Execution::Sleep(_)
| Execution::ClearRepoCache(_)
| Execution::BackupCoreDatabase(_)
| Execution::GlobalAutoUpdate(_) => {}
| Execution::ClearRepoCache(_) => {}
}
}
}
@@ -827,13 +823,8 @@ impl ToToml for Procedure {
for stage in stages {
toml.push_str("\n\n[[procedure.config.stage]]\n");
toml.push_str(
&toml_pretty::to_string(
stage,
// If the execution.params are fully missing,
// deserialization will fail.
TOML_PRETTY_OPTIONS.skip_empty_object(false),
)
.context("failed to serialize procedures to toml")?,
&toml_pretty::to_string(stage, TOML_PRETTY_OPTIONS)
.context("failed to serialize procedures to toml")?,
);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
FROM rust:1.89.0-bullseye AS builder
FROM rust:1.88.0-bullseye AS builder
WORKDIR /builder
COPY Cargo.toml Cargo.lock ./
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn periphery_config() -> &'static PeripheryConfig {
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
include_file_name: ".peripheryinclude",
include_file_name: ".peripheryignore",
merge_nested: args
.merge_nested_config
.unwrap_or(env.periphery_merge_nested_config),
+1 -8
View File
@@ -60,14 +60,7 @@ async fn update_container_stats() {
pub async fn get_container_stats(
container_name: Option<String>,
) -> anyhow::Result<Vec<ContainerStats>> {
let format = "--format '{\"BlockIO\":\"{{ .BlockIO }}\", \
\"CPUPerc\":\"{{ .CPUPerc }}\", \
\"ID\":\"{{ .ID }}\", \
\"MemPerc\":\"{{ .MemPerc }}\", \
\"MemUsage\":\"{{ .MemUsage }}\", \
\"Name\":\"{{ .Name }}\", \
\"NetIO\":\"{{ .NetIO }}\",\
\"PIDs\":\"{{ .PIDs }}\"}'";
let format = "--format \"{{ json . }}\"";
let container_name = match container_name {
Some(name) => format!(" {name}"),
None => "".to_string(),
+7 -10
View File
@@ -11,8 +11,6 @@ pub trait KomodoAuthRequest: HasResponse {}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JwtResponse {
/// User ID for signed in user.
pub user_id: String,
/// A token the user can use to authenticate their requests.
pub jwt: String,
}
@@ -49,20 +47,19 @@ pub struct GetLoginOptionsResponse {
//
/// Sign up a new local user account. Will fail if a user with the
/// Create a new local user account. Will fail if a user with the
/// given username already exists.
/// Response: [SignUpLocalUserResponse].
/// Response: [CreateLocalUserResponse].
///
/// Note. This method is only available if the core api has `local_auth` enabled,
/// and if user registration is not disabled (after the first user).
/// Note. This method is only available if the core api has `local_auth` enabled.
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoAuthRequest)]
#[response(SignUpLocalUserResponse)]
#[response(CreateLocalUserResponse)]
#[error(serror::Error)]
pub struct SignUpLocalUser {
pub struct CreateLocalUser {
/// The username for the new user.
pub username: String,
/// The password for the new user.
@@ -70,9 +67,9 @@ pub struct SignUpLocalUser {
pub password: String,
}
/// Response for [SignUpLocalUser].
/// Response for [CreateLocalUser].
#[typeshare]
pub type SignUpLocalUserResponse = JwtResponse;
pub type CreateLocalUserResponse = JwtResponse;
//
+1 -1
View File
@@ -30,7 +30,7 @@ pub struct RunAction {
/// Custom arguments which are merged on top of the default arguments.
/// CLI Format: `"VAR1=val1&VAR2=val2"`
///
///
/// Webhook-triggered actions use this to pass WEBHOOK_BRANCH and WEBHOOK_BODY.
#[clap(value_parser = args_parser)]
pub args: Option<JsonObject>,
+4 -28
View File
@@ -9,7 +9,6 @@ use crate::entities::update::Update;
use super::KomodoExecuteRequest;
/// Clears all repos from the Core repo cache. Admin only.
/// Response: [Update]
#[typeshare]
#[derive(
Debug,
@@ -26,14 +25,13 @@ use super::KomodoExecuteRequest;
#[error(serror::Error)]
pub struct ClearRepoCache {}
/// Backs up the Komodo Core database to compressed jsonl files.
/// Admin only. Response: [Update]
/// Backs up the database to compressed jsonl files. Admin only.
///
/// Mount a folder to `/backups`, and Core will use it to create
/// timestamped database dumps, which can be restored using
/// the Komodo CLI.
/// `ghcr.io/moghtech/komodo-util`.
///
/// https://komo.do/docs/setup/backup
/// TODO: Link to docs
#[typeshare]
#[derive(
Debug,
@@ -48,26 +46,4 @@ pub struct ClearRepoCache {}
#[empty_traits(KomodoExecuteRequest)]
#[response(Update)]
#[error(serror::Error)]
pub struct BackupCoreDatabase {}
/// Trigger a global poll for image updateson Stacks and Deployments
/// with `poll_for_updates` or `auto_update` enabled.
/// Admin only. Response: [Update]
///
/// 1. `docker compose pull` any Stacks / Deployments with `poll_for_updates` or `auto_update` enabled. This will pick up any available updates.
/// 2. Redeploy Stacks / Deployments that have updates found.
#[typeshare]
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Resolve,
EmptyTraits,
Parser,
)]
#[empty_traits(KomodoExecuteRequest)]
#[response(Update)]
#[error(serror::Error)]
pub struct GlobalAutoUpdate {}
pub struct BackupDatabase {}
+1 -3
View File
@@ -156,10 +156,8 @@ pub enum Execution {
// ALERTER
TestAlerter(TestAlerter),
// MAINTENANCE
// CORE
ClearRepoCache(ClearRepoCache),
BackupCoreDatabase(BackupCoreDatabase),
GlobalAutoUpdate(GlobalAutoUpdate),
// SLEEP
Sleep(Sleep),
-2
View File
@@ -20,8 +20,6 @@ use super::KomodoWriteRequest;
pub struct CreateTag {
/// The name of the tag.
pub name: String,
/// Tag color. Default: Slate.
pub color: Option<TagColor>,
}
//
-25
View File
@@ -69,31 +69,6 @@ pub type DeleteUserResponse = User;
//
/// **Admin only.** Create a local user.
/// Response: [User].
///
/// Note. Not to be confused with /auth/SignUpLocalUser.
/// This method requires admin user credentials, and can
/// bypass disabled user registration.
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(CreateLocalUserResponse)]
#[error(serror::Error)]
pub struct CreateLocalUser {
/// The username for the local user.
pub username: String,
/// A password for the local user.
pub password: String,
}
#[typeshare]
pub type CreateLocalUserResponse = User;
//
/// **Admin only.** Create a service user.
/// Response: [User].
#[typeshare]
+1 -5
View File
@@ -164,11 +164,7 @@ pub struct ActionConfig {
pub arguments_format: FileFormat,
/// Default arguments to give to the Action for use in the script at `ARGS`.
#[serde(default, deserialize_with = "file_contents_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_file_contents_deserializer"
))]
#[serde(default)]
#[builder(default)]
pub arguments: String,
}
@@ -14,9 +14,6 @@ pub struct Container {
/// List only non-running containers.
#[arg(long, short = 'd', default_value_t = false)]
pub down: bool,
/// Include links. Makes the table very large.
#[arg(long, short = 'l', default_value_t = false)]
pub links: bool,
/// Filter containers by a particular server.
/// Supports wildcard syntax.
/// Can be specified multiple times. (alias `s`)
@@ -51,22 +48,7 @@ pub struct InspectContainer {
/// The container name. If it matches multiple containers and no server is specified,
/// each container's inspect info will be logged.
pub container: String,
/// Select the particular server that container is on.
/// Select the particular server container is on (alias `s`)
#[arg(name = "server", long, short = 's')]
pub servers: Vec<String>,
/// Only show the .State part of the inspect response.
#[arg(long, short = 'u')]
pub state: bool,
/// Only show the .Mounts part of the inspect response.
#[arg(long, short = 'm')]
pub mounts: bool,
/// Only show the .HostConfig part of the inspect response.
#[arg(long, short = 'f')]
pub host_config: bool,
/// Only show the .Config part of the inspect response.
#[arg(long, short = 'c')]
pub config: bool,
/// Only show the .NetworkSettings part of the inspect response.
#[arg(long, short = 'n')]
pub network_settings: bool,
}
@@ -1,5 +1,3 @@
use crate::entities::resource::TemplatesQueryBehavior;
#[derive(Debug, Clone, clap::Parser)]
pub struct List {
/// List specific resources
@@ -15,24 +13,6 @@ pub struct List {
/// List only non-running / non-ok resources.
#[arg(long, short = 'd', default_value_t = false)]
pub down: bool,
/// List only "in progress" / "pending" resources, like Actions / Procedures that are running (alias: `pending`)
#[arg(
long,
short = 'p',
alias = "pending",
default_value_t = false
)]
pub in_progress: bool,
/// Include links. Makes the table very large.
#[arg(long, short = 'l', default_value_t = false)]
pub links: bool,
/// Whether to include resources marked as templates in results. Default: 'exclude'.
#[arg(
long,
short = 'm',
default_value_t = TemplatesQueryBehavior::Exclude,
)]
pub templates: TemplatesQueryBehavior,
/// Filter by a particular name. Supports wildcard.
/// Can be specified multiple times. (alias `n`)
#[arg(name = "name", long, short = 'n')]
@@ -60,9 +40,6 @@ impl From<List> for ResourceFilters {
all: value.all,
reverse: value.reverse,
down: value.down,
in_progress: value.in_progress,
links: value.links,
templates: value.templates,
names: value.names,
tags: value.tags,
servers: value.servers,
@@ -121,24 +98,6 @@ pub struct ResourceFilters {
/// List only non-running / non-ok resources.
#[arg(long, short = 'd', default_value_t = false)]
pub down: bool,
/// List only "in progress" / "pending" resources, like Actions / Procedures that are running (alias: `pending`)
#[arg(
long,
short = 'p',
alias = "pending",
default_value_t = false
)]
pub in_progress: bool,
/// Include links. Makes the table very large.
#[arg(long, short = 'l', default_value_t = false)]
pub links: bool,
/// Whether to include resources marked as templates in results. Default: 'exclude'.
#[arg(
long,
short = 'm',
default_value_t = TemplatesQueryBehavior::Exclude,
)]
pub templates: TemplatesQueryBehavior,
/// Filter by a particular name. Supports wildcard.
/// Can be specified multiple times. (alias `n`)
#[arg(name = "name", long, short = 'n')]
@@ -32,7 +32,7 @@ pub struct CliArgs {
pub config_keyword: Option<Vec<String>>,
/// Whether to debug print on configuration load (on startup)
#[arg(alias = "debug", long, short = 'd')]
#[arg(action, alias = "debug", short = 'd')]
pub debug_startup: Option<bool>,
}
@@ -64,13 +64,10 @@ pub struct Env {
pub komodo_cli_key: Option<String>,
/// Override `cli_secret`
pub komodo_cli_secret: Option<String>,
/// Override `table_borders`
pub komodo_cli_table_borders: Option<CliTableBorders>,
/// Override `backups_folder`
pub komodo_cli_backups_folder: Option<PathBuf>,
/// Override `max_backups`
pub komodo_cli_max_backups: Option<u16>,
/// Override `database_target_uri`
#[serde(alias = "komodo_cli_database_copy_uri")]
pub komodo_cli_database_target_uri: Option<String>,
@@ -191,9 +188,6 @@ pub struct CliConfig {
/// The api secret for the CLI to use
#[serde(alias = "secret", skip_serializing_if = "Option::is_none")]
pub cli_secret: Option<String>,
/// The format for the tables.
#[serde(skip_serializing_if = "Option::is_none")]
pub table_borders: Option<CliTableBorders>,
/// The root backups folder.
///
/// Default: `/backups`.
@@ -277,7 +271,6 @@ impl Default for CliConfig {
cli_key: Default::default(),
cli_secret: Default::default(),
cli_logging: default_log_config(),
table_borders: Default::default(),
backups_folder: default_backups_folder(),
max_backups: default_max_backups(),
database: default_database_config(),
@@ -303,7 +296,6 @@ impl CliConfig {
.as_ref()
.map(|cli_secret| empty_or_redacted(cli_secret)),
cli_logging: self.cli_logging.clone(),
table_borders: self.table_borders,
backups_folder: self.backups_folder.clone(),
max_backups: self.max_backups,
database_target: self.database_target.sanitized(),
@@ -317,18 +309,3 @@ impl CliConfig {
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum CliTableBorders {
/// Only horizontal borders. Default.
#[default]
Horizontal,
/// Only vertical borders.
Vertical,
/// Only borders around the outside of the table.
Outside,
/// Only borders horizontally / vertically between the rows / columns.
Inside,
/// All borders
All,
}
+7 -66
View File
@@ -80,8 +80,6 @@ pub struct Env {
pub komodo_timezone: Option<String>,
/// Override `first_server`
pub komodo_first_server: Option<String>,
/// Override `first_server_name`
pub komodo_first_server_name: Option<String>,
/// Override `frontend_path`
pub komodo_frontend_path: Option<String>,
/// Override `jwt_secret`
@@ -142,21 +140,11 @@ pub struct Env {
pub komodo_disable_non_admin_create: Option<bool>,
/// Override `disable_websocket_reconnect`
pub komodo_disable_websocket_reconnect: Option<bool>,
/// Override `disable_init_resources`
pub komodo_disable_init_resources: Option<bool>,
/// Override `enable_fancy_toml`
pub komodo_enable_fancy_toml: Option<bool>,
/// Override `local_auth`
pub komodo_local_auth: Option<bool>,
/// Override `init_admin_username`
pub komodo_init_admin_username: Option<String>,
/// Override `init_admin_username` from file
pub komodo_init_admin_username_file: Option<PathBuf>,
/// Override `init_admin_password`
pub komodo_init_admin_password: Option<String>,
/// Override `init_admin_password` from file
pub komodo_init_admin_password_file: Option<PathBuf>,
/// Override `oidc_enabled`
pub komodo_oidc_enabled: Option<bool>,
@@ -338,24 +326,14 @@ pub struct CoreConfig {
#[serde(default)]
pub disable_websocket_reconnect: bool,
/// Disable init system resource creation on fresh Komodo launch.
/// These include the Backup Core Database and Global Auto Update procedures.
#[serde(default)]
pub disable_init_resources: bool,
/// Enable the fancy TOML syntax highlighting
#[serde(default)]
pub enable_fancy_toml: bool,
/// If defined, ensure an enabled first server exists at this address.
/// Example: `http://periphery:8120`
#[serde(skip_serializing_if = "Option::is_none")]
pub first_server: Option<String>,
/// Give the first server this name.
/// Default: `Local`
#[serde(default = "default_first_server_name")]
pub first_server_name: String,
#[serde(default)]
pub first_server: String,
/// The path to the built frontend folder.
#[serde(default = "default_frontend_path")]
@@ -372,15 +350,6 @@ pub struct CoreConfig {
#[serde(default)]
pub local_auth: bool,
/// Upon fresh launch, initalize an Admin user with this username.
/// If this is not provided, no initial user will be created.
#[serde(skip_serializing_if = "Option::is_none")]
pub init_admin_username: Option<String>,
/// Upon fresh launch, initalize an Admin user with this password.
/// Default: `changeme`
#[serde(default = "default_init_admin_password")]
pub init_admin_password: String,
/// Enable transparent mode, which gives all (enabled) users read access to all resources.
#[serde(default)]
pub transparent_mode: bool,
@@ -399,7 +368,7 @@ pub struct CoreConfig {
/// APIs are disabled. Used by demo to lock the 'demo' : 'demo' login.
///
/// To lock the api for all users, use `lock_login_credentials_for = ["__ALL__"]`
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub lock_login_credentials_for: Vec<String>,
/// Normally all users can create resources.
@@ -460,7 +429,7 @@ pub struct CoreConfig {
/// Your OIDC provider may set additional audiences other than `client_id`,
/// they must be added here to make claims verification work.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub oidc_additional_audiences: Vec<String>,
// =========
@@ -550,11 +519,7 @@ pub struct CoreConfig {
// =================
/// Configure git credentials used to clone private repos.
/// Supports any git provider.
#[serde(
default,
alias = "git_provider",
skip_serializing_if = "Vec::is_empty"
)]
#[serde(default, alias = "git_provider")]
pub git_providers: Vec<GitProvider>,
// ======================
@@ -562,11 +527,7 @@ pub struct CoreConfig {
// ======================
/// Configure docker credentials used to push / pull images.
/// Supports any docker image repository.
#[serde(
default,
alias = "docker_registry",
skip_serializing_if = "Vec::is_empty"
)]
#[serde(default, alias = "docker_registry")]
pub docker_registries: Vec<DockerRegistry>,
// ===========
@@ -575,7 +536,7 @@ pub struct CoreConfig {
/// Configure core-based secrets. These will be preferentially interpolated into
/// values if they contain a matching secret. Otherwise, the periphery will have to have the
/// secret configured.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
pub secrets: HashMap<String, String>,
// =======
@@ -639,18 +600,10 @@ fn default_frontend_path() -> String {
"/app/frontend".to_string()
}
fn default_first_server_name() -> String {
String::from("Local")
}
fn default_jwt_ttl() -> Timelength {
Timelength::OneDay
}
fn default_init_admin_password() -> String {
String::from("changeme")
}
fn default_sync_directory() -> PathBuf {
// unwrap ok: `/syncs` will always be valid path
PathBuf::from_str("/syncs").unwrap()
@@ -699,15 +652,11 @@ impl Default for CoreConfig {
ui_write_disabled: Default::default(),
disable_confirm_dialog: Default::default(),
disable_websocket_reconnect: Default::default(),
disable_init_resources: Default::default(),
enable_fancy_toml: Default::default(),
first_server: Default::default(),
first_server_name: default_first_server_name(),
frontend_path: default_frontend_path(),
database: Default::default(),
local_auth: Default::default(),
init_admin_username: Default::default(),
init_admin_password: default_init_admin_password(),
transparent_mode: Default::default(),
enable_new_users: Default::default(),
disable_user_registration: Default::default(),
@@ -758,7 +707,6 @@ impl CoreConfig {
passkey: empty_or_redacted(&config.passkey),
timezone: config.timezone,
first_server: config.first_server,
first_server_name: config.first_server_name,
frontend_path: config.frontend_path,
jwt_secret: empty_or_redacted(&config.jwt_secret),
jwt_ttl: config.jwt_ttl,
@@ -776,19 +724,12 @@ impl CoreConfig {
ui_write_disabled: config.ui_write_disabled,
disable_confirm_dialog: config.disable_confirm_dialog,
disable_websocket_reconnect: config.disable_websocket_reconnect,
disable_init_resources: config.disable_init_resources,
enable_fancy_toml: config.enable_fancy_toml,
enable_new_users: config.enable_new_users,
disable_user_registration: config.disable_user_registration,
disable_non_admin_create: config.disable_non_admin_create,
lock_login_credentials_for: config.lock_login_credentials_for,
local_auth: config.local_auth,
init_admin_username: config
.init_admin_username
.map(|u| empty_or_redacted(&u)),
init_admin_password: empty_or_redacted(
&config.init_admin_password,
),
oidc_enabled: config.oidc_enabled,
oidc_provider: config.oidc_provider,
oidc_redirect_host: config.oidc_redirect_host,
-2
View File
@@ -1165,8 +1165,6 @@ pub enum Operation {
// maintenance
ClearRepoCache,
BackupCoreDatabase,
GlobalAutoUpdate,
// variable
CreateVariable,
+1 -14
View File
@@ -1,9 +1,7 @@
use bson::{Document, doc};
use clap::ValueEnum;
use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use serde::{Deserialize, Serialize};
use strum::Display;
use typeshare::typeshare;
use crate::{
@@ -126,18 +124,7 @@ pub struct ResourceQuery<T: Default> {
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
Default,
Serialize,
Deserialize,
ValueEnum,
Display,
)]
// Only strum serializes lowercase for clap compat.
#[strum(serialize_all = "lowercase")]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum TemplatesQueryBehavior {
/// Include templates in results. Default.
#[default]
+1 -12
View File
@@ -34,10 +34,6 @@ pub struct ServerListItemInfo {
pub region: String,
/// Address of the server.
pub address: String,
/// External address of the server (reachable by users).
/// Used with links.
#[serde(default)] // API backward compat
pub external_address: String,
/// The Komodo Periphery version of the server.
pub version: String,
/// Whether server is configured to send unreachable alerts.
@@ -70,12 +66,6 @@ pub struct ServerConfig {
#[partial_default(default_address())]
pub address: String,
/// The address to use with links for containers on the server.
/// If empty, will use the 'address' for links.
#[serde(default)]
#[builder(default)]
pub external_address: String,
/// An optional region label
#[serde(default)]
#[builder(default)]
@@ -260,8 +250,7 @@ fn default_disk_critical() -> f64 {
impl Default for ServerConfig {
fn default() -> Self {
Self {
address: default_address(),
external_address: Default::default(),
address: Default::default(),
enabled: default_enabled(),
timeout_seconds: default_timeout_seconds(),
ignore_mounts: Default::default(),
+8 -13
View File
@@ -41,21 +41,16 @@ pub struct Tag {
#[serde(default)]
#[builder(default)]
pub color: TagColor,
// /// This field is not stored on database,
// /// but rather populated at query time based on results from the other resources.
// #[serde(default, skip_serializing_if = "is_false")]
// #[builder(default)]
// pub unused: bool,
/// This field is not stored on database,
/// but rather populated at query time based on results from the other resources.
#[serde(default, skip_serializing_if = "is_false")]
#[builder(default)]
pub unused: bool,
}
// fn is_false(b: &bool) -> bool {
// !b
// }
impl Tag {
pub fn builder() -> TagBuilder {
TagBuilder::default()
}
fn is_false(b: &bool) -> bool {
!b
}
#[typeshare]
+5 -8
View File
@@ -2,7 +2,7 @@ import * as Types from "./types.js";
export type AuthResponses = {
GetLoginOptions: Types.GetLoginOptionsResponse;
SignUpLocalUser: Types.SignUpLocalUserResponse;
CreateLocalUser: Types.CreateLocalUserResponse;
LoginLocalUser: Types.LoginLocalUserResponse;
ExchangeForJwt: Types.ExchangeForJwtResponse;
GetUser: Types.GetUserResponse;
@@ -179,10 +179,9 @@ export type ReadResponses = {
export type WriteResponses = {
// ==== USER ====
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
UpdateUserUsername: Types.UpdateUserUsername;
UpdateUserPassword: Types.UpdateUserPassword;
DeleteUser: Types.DeleteUser;
// ==== SERVICE USER ====
CreateServiceUser: Types.CreateServiceUserResponse;
@@ -293,7 +292,7 @@ export type WriteResponses = {
DeleteResourceSync: Types.ResourceSync;
UpdateResourceSync: Types.ResourceSync;
RenameResourceSync: Types.Update;
CommitSync: Types.Update;
CommitSync: Types.ResourceSync;
WriteSyncFileContents: Types.Update;
RefreshResourceSyncPending: Types.ResourceSync;
CreateSyncWebhook: Types.CreateSyncWebhookResponse;
@@ -411,6 +410,4 @@ export type ExecuteResponses = {
// ==== MAINTENANCE ====
ClearRepoCache: Types.Update;
BackupCoreDatabase: Types.Update;
GlobalAutoUpdate: Types.Update;
};
+34 -85
View File
@@ -448,8 +448,6 @@ export enum Operation {
CommitSync = "CommitSync",
RunSync = "RunSync",
ClearRepoCache = "ClearRepoCache",
BackupCoreDatabase = "BackupCoreDatabase",
GlobalAutoUpdate = "GlobalAutoUpdate",
CreateVariable = "CreateVariable",
UpdateVariableValue = "UpdateVariableValue",
DeleteVariable = "DeleteVariable",
@@ -869,8 +867,6 @@ export type Execution =
| { type: "BatchDestroyStack", params: BatchDestroyStack }
| { type: "TestAlerter", params: TestAlerter }
| { type: "ClearRepoCache", params: ClearRepoCache }
| { type: "BackupCoreDatabase", params: BackupCoreDatabase }
| { type: "GlobalAutoUpdate", params: GlobalAutoUpdate }
| { type: "Sleep", params: Sleep };
/** Allows to enable / disabled procedures in the sequence / parallel vec on the fly */
@@ -1024,6 +1020,19 @@ export interface GitProviderAccount {
export type CreateGitProviderAccountResponse = GitProviderAccount;
/** JSON containing an authentication token. */
export interface JwtResponse {
/** A token the user can use to authenticate their requests. */
jwt: string;
}
/** Response for [CreateLocalUser]. */
export type CreateLocalUserResponse = JwtResponse;
export type CreateProcedureResponse = Procedure;
export type CreateRepoWebhookResponse = NoData;
export type UserConfig =
/** User that logs in with username / password */
| { type: "Local", data: {
@@ -1079,12 +1088,6 @@ export interface User {
updated_at?: I64;
}
export type CreateLocalUserResponse = User;
export type CreateProcedureResponse = Procedure;
export type CreateRepoWebhookResponse = NoData;
export type CreateServiceUserResponse = User;
export type CreateStackWebhookResponse = NoData;
@@ -1321,14 +1324,6 @@ export interface DeploymentQuerySpecifics {
export type DeploymentQuery = ResourceQuery<DeploymentQuerySpecifics>;
/** JSON containing an authentication token. */
export interface JwtResponse {
/** User ID for signed in user. */
user_id: string;
/** A token the user can use to authenticate their requests. */
jwt: string;
}
/** Response for [ExchangeForJwt]. */
export type ExchangeForJwtResponse = JwtResponse;
@@ -1953,11 +1948,6 @@ export interface ServerConfig {
* Default: http://localhost:8120
*/
address: string;
/**
* The address to use with links for containers on the server.
* If empty, will use the 'address' for links.
*/
external_address?: string;
/** An optional region label */
region?: string;
/**
@@ -2464,6 +2454,11 @@ export interface Tag {
owner?: string;
/** Hex color code with alpha for UI display */
color?: TagColor;
/**
* This field is not stored on database,
* but rather populated at query time based on results from the other resources.
*/
unused?: boolean;
}
export type GetTagResponse = Tag;
@@ -3745,11 +3740,6 @@ export interface ServerListItemInfo {
region: string;
/** Address of the server. */
address: string;
/**
* External address of the server (reachable by users).
* Used with links.
*/
external_address?: string;
/** The Komodo Periphery version of the server. */
version: string;
/** Whether server is configured to send unreachable alerts. */
@@ -3957,9 +3947,6 @@ export type ServerQuery = ResourceQuery<ServerQuerySpecifics>;
export type SetLastSeenUpdateResponse = NoData;
/** Response for [SignUpLocalUser]. */
export type SignUpLocalUserResponse = JwtResponse;
export interface StackQuerySpecifics {
/**
* Query only for Stacks on these Servers.
@@ -4100,16 +4087,15 @@ export interface AwsBuilderConfig {
}
/**
* Backs up the Komodo Core database to compressed jsonl files.
* Admin only. Response: [Update]
* Backs up the database to compressed jsonl files. Admin only.
*
* Mount a folder to `/backups`, and Core will use it to create
* timestamped database dumps, which can be restored using
* the Komodo CLI.
* `ghcr.io/moghtech/komodo-util`.
*
* TODO: Link to docs
*/
export interface BackupCoreDatabase {
export interface BackupDatabase {
}
/** Builds multiple Repos in parallel that match pattern. Response: [BatchExecutionResponse]. */
@@ -4365,10 +4351,7 @@ export interface CancelRepoBuild {
repo: string;
}
/**
* Clears all repos from the Core repo cache. Admin only.
* Response: [Update]
*/
/** Clears all repos from the Core repo cache. Admin only. */
export interface ClearRepoCache {
}
@@ -4904,17 +4887,19 @@ export interface CreateGitProviderAccount {
}
/**
* **Admin only.** Create a local user.
* Response: [User].
* Create a new local user account. Will fail if a user with the
* given username already exists.
* Response: [CreateLocalUserResponse].
*
* Note. Not to be confused with /auth/SignUpLocalUser.
* This method requires admin user credentials, and can
* bypass disabled user registration.
* Note. This method is only available if the core api has `local_auth` enabled.
*/
export interface CreateLocalUser {
/** The username for the local user. */
/** The username for the new user. */
username: string;
/** A password for the local user. */
/**
* The password for the new user.
* This cannot be retreived later.
*/
password: string;
}
@@ -5035,8 +5020,6 @@ export interface CreateSyncWebhook {
export interface CreateTag {
/** The name of the tag. */
name: string;
/** Tag color. Default: Slate. */
color?: TagColor;
}
/**
@@ -6439,17 +6422,6 @@ export interface GetVersionResponse {
version: string;
}
/**
* Trigger a global poll for image updateson Stacks and Deployments
* with `poll_for_updates` or `auto_update` enabled.
* Admin only. Response: [Update]
*
* 1. `docker compose pull` any Stacks / Deployments with `poll_for_updates` or `auto_update` enabled. This will pick up any available updates.
* 2. Redeploy Stacks / Deployments that have updates found.
*/
export interface GlobalAutoUpdate {
}
/**
* Inspect the docker container associated with the Deployment.
* Response: [Container].
@@ -7551,9 +7523,7 @@ export interface RunAction {
/** Id or name */
action: string;
/**
* Custom arguments which are merged on top of the default arguments.
* CLI Format: `"VAR1=val1&VAR2=val2"`
*
* Custom arguments which can override the defaults.
* Webhook-triggered actions use this to pass WEBHOOK_BRANCH and WEBHOOK_BODY.
*/
args?: JsonObject;
@@ -7736,24 +7706,6 @@ export interface SetUsersInUserGroup {
users: string[];
}
/**
* Sign up a new local user account. Will fail if a user with the
* given username already exists.
* Response: [SignUpLocalUserResponse].
*
* Note. This method is only available if the core api has `local_auth` enabled,
* and if user registration is not disabled (after the first user).
*/
export interface SignUpLocalUser {
/** The username for the new user. */
username: string;
/**
* The password for the new user.
* This cannot be retreived later.
*/
password: string;
}
/** Info for network interface usage. */
export interface SingleNetworkInterfaceUsage {
/** The network interface name */
@@ -8312,7 +8264,7 @@ export interface WriteSyncFileContents {
export type AuthRequest =
| { type: "GetLoginOptions", params: GetLoginOptions }
| { type: "SignUpLocalUser", params: SignUpLocalUser }
| { type: "CreateLocalUser", params: CreateLocalUser }
| { type: "LoginLocalUser", params: LoginLocalUser }
| { type: "ExchangeForJwt", params: ExchangeForJwt }
| { type: "GetUser", params: GetUser };
@@ -8389,9 +8341,7 @@ export type ExecuteRequest =
| { type: "BatchRunAction", params: BatchRunAction }
| { type: "TestAlerter", params: TestAlerter }
| { type: "RunSync", params: RunSync }
| { type: "ClearRepoCache", params: ClearRepoCache }
| { type: "BackupCoreDatabase", params: BackupCoreDatabase }
| { type: "GlobalAutoUpdate", params: GlobalAutoUpdate };
| { type: "ClearRepoCache", params: ClearRepoCache };
/**
* One representative IANA zone for each distinct base UTC offset in the tz database.
@@ -8653,7 +8603,6 @@ export type UserRequest =
| { type: "DeleteApiKey", params: DeleteApiKey };
export type WriteRequest =
| { type: "CreateLocalUser", params: CreateLocalUser }
| { type: "UpdateUserUsername", params: UpdateUserUsername }
| { type: "UpdateUserPassword", params: UpdateUserPassword }
| { type: "DeleteUser", params: DeleteUser }
-8
View File
@@ -38,8 +38,6 @@ KOMODO_TITLE=Komodo
## Create a server matching this address as the "first server".
## Use `https://host.docker.internal:8120` when using systemd-managed Periphery.
KOMODO_FIRST_SERVER=https://periphery:8120
## Give the first server a custom name.
KOMODO_FIRST_SERVER_NAME=Local
## Make all buttons just double-click, rather than the full confirmation dialog.
KOMODO_DISABLE_CONFIRM_DIALOG=false
@@ -63,12 +61,6 @@ KOMODO_JWT_TTL="1-day"
## Enable login with username + password.
KOMODO_LOCAL_AUTH=true
## Set the initial admin username created upon first launch.
## Comment out to disable initial user creation,
## and create first user using signup button.
KOMODO_INIT_ADMIN_USERNAME=admin
## Set the initial admin password
KOMODO_INIT_ADMIN_PASSWORD=changeme
## Disable new user signups.
KOMODO_DISABLE_USER_REGISTRATION=false
## All new logins are auto enabled
+1
View File
@@ -0,0 +1 @@
.
-22
View File
@@ -55,11 +55,6 @@ passkey = "default-passkey-changeme"
## Optional, no default.
first_server = ""
## Give the first server a custom name.
## Env: KOMODO_FIRST_SERVER_NAME
## Default: Local
first_server_name = "Local"
## Disables write support on resources in the UI.
## This protects users that that would normally have write priviledges during their UI usage,
## when they intend to fully rely on ResourceSyncs to manage config.
@@ -79,13 +74,6 @@ disable_confirm_dialog = false
## Default: false
disable_websocket_reconnect = false
## Disable init system resource creation on fresh Komodo launch.
## These include the 'Backup Core Database' and 'Global Auto Update' procedures.
## Env: KOMODO_DISABLE_INIT_RESOURCES
## Default: false
disable_init_resources = false
## Configure the directory for sync files (inside the container).
## There shouldn't be a need to change this, just mount a volume.
## Env: KOMODO_SYNC_DIRECTORY
@@ -127,16 +115,6 @@ internet_interface = ""
## Default: false
local_auth = false
## Initialize the first admin user when starting up Komodo for the first time.
## Env: KOMODO_INIT_ADMIN_USERNAME or KOMODO_INIT_ADMIN_USERNAME_FILE
## Default: None
# init_admin_username = "admin"
## Set password for first admin user
## Env: KOMODO_INIT_ADMIN_PASSWORD or KOMODO_INIT_ADMIN_PASSWORD_FILE
## Default: changeme
init_admin_password = "changeme"
## Normally new users will be registered, but not enabled until an Admin enables them.
## With `disable_user_registration = true`, only the first user to log in will registered as a user.
## Env: KOMODO_DISABLE_USER_REGISTRATION
-5
View File
@@ -18,13 +18,8 @@ default_profile = "Default"
# default_profile = "Alt"
# Set base values if they aren't defined in profile
# Default: 14
max_backups = 7
# Options: HorizontalOnly, VeriticalOnly, OutsideOnly, InsideOnly, AllBorders
# Default: HorizontalOnly
table_format = "HorizontalOnly"
[[profile]]
name = "Default"
aliases = ["d"] # Use `km -p d ...`
+10 -7
View File
@@ -1,11 +1,5 @@
import * as TOML from "jsr:@std/toml";
const branch = await new Deno.Command("bash", {
args: ["-c", "git rev-parse --abbrev-ref HEAD"],
})
.output()
.then((r) => new TextDecoder("utf-8").decode(r.stdout).trim());
const cargo_toml_str = await Deno.readTextFile("Cargo.toml");
const prev_version = (
TOML.parse(cargo_toml_str) as {
@@ -38,7 +32,16 @@ echo ""
git push
echo ""
km run -y action deploy-komodo "KOMODO_BRANCH=${branch}&KOMODO_VERSION=${version}&KOMODO_TAG=${tag}-${next_count}"
km set var KOMODO_VERSION ${version} -y
echo ""
km set var KOMODO_TAG ${tag}-${next_count} -y
echo ""
km run -y commit variables
echo ""
km run -y action deploy-komodo
`
.split("\n")
.map((line) => line.trim())
@@ -2,7 +2,7 @@
Komodo Core exposes an RPC-like HTTP API to read data, write configuration, and execute actions.
There are typesafe clients available in
[**Rust**](/docs/ecosystem/api#rust-client) and [**Typescript**](/docs/ecosystem/api#typescript-client).
[**Rust**](/docs/api#rust-client) and [**Typescript**](/docs/api#typescript-client).
The full API documentation is [**available here**](https://docs.rs/komodo_client/latest/komodo_client/api/index.html).
@@ -5,8 +5,8 @@ Komodo just needs a bit of information in order to build your image.
### Provider configuration
Komodo supports cloning repos over http/s, from any provider that supports cloning private repos using `git clone https://<Token>@git-provider.net/<Owner>/<Repo>`.
Accounts / access tokens can be configured in either the [core config](../../setup/advanced.mdx#mount-a-config-file)
or in the [periphery config](../../setup/connect-servers.mdx#manual-install-steps---binaries).
Accounts / access tokens can be configured in either the [core config](../setup/advanced.mdx#mount-a-config-file)
or in the [periphery config](../connect-servers.mdx#manual-install-steps---binaries).
### Repo configuration
To specify the git repo to build, just give it the name of the repo and the branch under *repo config*. The name is given like `moghtech/komodo`, it includes the username / organization that owns the repo.
@@ -1,6 +1,6 @@
# Komodo CLI
The Komodo CLI, `km`, can be used to:
The Komodo CLI (`km`) can be used to:
- Quickly **run executions** and update **resources** and **variables**.
- **Reset user passwords** and elevate users to **Super Admin**.
- Perform Database **backup**, **restore**, and **copy**.
@@ -24,11 +24,6 @@ This way, it inherits the Core database config in order to easily perform backup
### Install
There are binaries available for **Linux** (x86_64 / aarch64), **MacOS** (apple silicon), as
well as a distroless image: **`ghcr.io/moghtech/komodo-cli`**.
#### Linux
You can install the binary using the following command:
System-wide, as root, to `/usr/local/bin/km`:
@@ -36,26 +31,13 @@ System-wide, as root, to `/usr/local/bin/km`:
curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/install-cli.py | python3
```
Or as non-root, to `${HOME}/.local/bin/km`:
Or as non-root, to `${HOME}/.local/bin/km`
```bash
curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/install-cli.py | python3 - --user
```
#### MacOS (Homebrew)
Add the `moghtech/komodo` tap, then install `km`:
```bash
brew tap moghtech/komodo && \
brew install km
```
#### Container
You can alias a docker run command:
```bash
alias km='docker run --rm -v $HOME/.config/komodo:/config ghcr.io/moghtech/komodo-cli km'
km config
```
The CLI is also available packaged in into multi-arch docker images.
- `ghcr.io/moghtech/komodo-cli`.
### Configure
@@ -72,20 +54,6 @@ When run, CLI will scan the **current working directory** in addition to `${HOME
parse them into a general representation, and then merge them together. Files which are detected later are merged later,
meaning they will override on conflicting fields. By default, files in `${HOME}/.config/komodo` come first
in the merge ordering, meaning they are **lower priority** than those detected in the current working directory.
You can also override these default paths by passing `km -c /path/to/1/base.config.yaml -c ./overrides ...`.
If you want `km` to find configuration files in another directory,
you can use make `.kminclude` file inside one of the configured directories.
```
# Supports comments
./.komodo # relative to directory containing `kmignore`
/etc/komodo/komodo.cli.toml
```
Note that wildcards in these paths are **not supported**.
#### Profiles
@@ -115,7 +83,7 @@ import RemoteCodeFile from "@site/src/components/RemoteCodeFile";
<RemoteCodeFile
title="https://github.com/moghtech/komodo/blob/main/config/komodo.cli.toml"
url="https://raw.githubusercontent.com/moghtech/komodo/main/config/komodo.cli.toml"
url="https://raw.githubusercontent.com/moghtech/komodo/1.19.0/config/komodo.cli.toml"
language="toml"
/>
```
@@ -39,11 +39,6 @@ You can find more information (and view the script) in the [readme](https://gith
This script can be run multiple times without issue, and it won't change existing config after the first run. Just run it again after a Komodo version release, and it will update the periphery version.
:::
:::tip
For deployment to many servers, a tool like [Ansible](https://docs.ansible.com/) should be used.
An example of such a setup can be found here: https://github.com/bpbradley/ansible-role-komodo
:::
### Install the Periphery agent - container
You can use a docker compose file:
@@ -22,7 +22,7 @@ Running Komodo from [source](https://github.com/moghtech/komodo) requires either
## Docker
After making changes to the project, run `run dev-compose-build` to rebuild Komodo and then `run dev-compose-exposed` to start a Komodo container with the UI accessible at `localhost:9120`. Any changes made to source files will require re-running the `dev-compose-build` and `dev-compose-exposed` commands.
After making changes to the project, run `run -r dev-compose-build` to rebuild Komodo and then `run -r dev-compose-exposed` to start a Komodo container with the UI accessible at `localhost:9120`. Any changes made to source files will require re-running the `dev-compose-build` and `dev-compose-exposed` commands.
## Devcontainer
@@ -39,17 +39,17 @@ To run a full Komodo instance from a non-container environment run commands in t
* Ensure dependencies are up to date
* `rustup update` -- ensure rust toolchain is up to date
* Build and Run backend
* `run dev-core` -- Build and run Core API
* `run dev-periphery` -- Build and run Periphery API
* `run -r dev-core` -- Build and run Core API
* `run -r dev-periphery` -- Build and run Periphery API
* Build Frontend
* Install **typeshare-cli**: `cargo install typeshare-cli`
* **Run this once** -- `run link-client` -- generates TS client and links to the frontend
* **Run this once** -- `run -r link-client` -- generates TS client and links to the frontend
* After running the above once:
* `run gen-client` -- Rebuild client
* `run dev-frontend` -- Start in dev (watch) mode
* `run build-frontend` -- Typecheck and build
* `run -r gen-client` -- Rebuild client
* `run -r dev-frontend` -- Start in dev (watch) mode
* `run -r build-frontend` -- Typecheck and build
## Docsite Development
Use `run dev-docsite` to start the [Docusaurus](https://docusaurus.io/) Komodo docs site in development mode. Changes made to files in `./docsite` will be automatically reloaded by the server.
Use `run -r dev-docsite` to start the [Docusaurus](https://docusaurus.io/) Komodo docs site in development mode. Changes made to files in `./docsite` will be automatically reloaded by the server.
-11
View File
@@ -1,11 +0,0 @@
---
slug: /ecosystem
---
# Ecosystem
```mdx-code-block
import DocCardList from '@theme/DocCardList';
<DocCardList />
```
+30
View File
@@ -0,0 +1,30 @@
# File Paths
When working with Komodo, you might have to configure file or directory paths.
## Relative Paths
Where possible, it is better to use relative file paths. Using relative file paths removes the connection between the process being run and the particular server it runs on, making it easier to move things between servers.
Where you see relative paths:
- setting the build directory and path of the Dockerfile
- setting a pre build command path
For all of the above, the path can be given relative to the root of the configured repo
The one exception is the Dockerfile path, which is given relative to the build directory (This is done by Docker itself, and this pattern matches usage of the Docker CLI).
There are 3 kinds of paths to pass:
1. to specify the root of the repo, use `.` as the path
2. to specify a folder in the repo, pass it with **no** preceding `/`. For example, `example_folder` or `folder1/folder2`
3. to specify an absolute path on the servers filesystem, use a preceding slash, eg. `/home/ubuntu/example`. This way should only be used if absolutely necessary, like when passing host paths when configuring docker volumes.
### Implementation
Relative file paths are joined with the path of the repo on the system using a Rust [PathBuf](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push).
## Docker Volume Paths
These are passed directly to the Docker CLI using `--volume /path/on/system:/path/in/container`. So for these, the same rules apply as when using Docker on the command line. Paths here should usually be given as absolute. It's also probably best to avoid usage of `~` or environment variables like `$HOME`, as this may lead to unexpected behavior.
+1 -1
View File
@@ -43,6 +43,6 @@ Komodo exposes powerful functionality over the Core's REST and Websocket API, en
## Permissioning
Komodo is a system designed to be used by many users, whether they are developers, operations personnel, or administrators. The ability to affect an applications state is very powerful, so Komodo has a granular permissioning system to only provide this functionality to the intended users. The permissioning system is explained in detail in the [permissioning](/docs/resources/permissioning) section.
Komodo is a system designed to be used by many users, whether they are developers, operations personnel, or administrators. The ability to affect an applications state is very powerful, so Komodo has a granular permissioning system to only provide this functionality to the intended users. The permissioning system is explained in detail in the [permissioning](/docs/permissioning) section.
User sign-on is possible using username / password, or with Oauth (Github and Google). See [Core Setup](./setup/index.mdx).
@@ -1,8 +1,5 @@
# Community
# Other Resources
### 3rd party tools
- [Ansible Role Komodo](https://github.com/bpbradley/ansible-role-komodo) by [bpbradley](https://github.com/bpbradley)
- [Komodo Import](https://foxxmd.github.io/komodo-import/docs/quickstart/) by [FoxxMD](https://github.com/FoxxMD)
### Posts and Guides
- [Migrating to Komodo](https://blog.foxxmd.dev/posts/migrating-to-komodo) by [FoxxMD](https://github.com/FoxxMD)
- [FAQ, Tips, and Tricks](https://blog.foxxmd.dev/posts/komodo-tips-tricks) by [FoxxMD](https://github.com/FoxxMD)
@@ -9,7 +9,7 @@ While Komodo can assign permissions to specific users directly, it is recommende
Users can then be **added to multiple User Groups** and they **inherit the group's permissions**, similar to linux permissions.
There is also an `Everyone` mode for User Groups, if this is enabled then **all users implicitly gain the groups permissions**.
For permissioning at scale, users can define [**User Groups in Resource Syncs**](/docs/resources/sync-resources#user-group).
For permissioning at scale, users can define [**User Groups in Resource Syncs**](/docs/sync-resources#user-group).
## Permission Levels
@@ -1,7 +1,6 @@
# Procedures and Actions
For orchestrations involving multiple resources and executions,
Komodo offers the `Procedure` and `Action` resource types.
For orchestrations involving multiple Resources, Komodo offers the `Procedure` and `Action` resource types.
## Procedures
@@ -10,18 +10,18 @@ Many resources need access to git repos / docker registries. There is an in-buil
All resources which depend on git repos / docker registries are able to use these credentials to access private repos.
:::
## [Server](setup/connect-servers)
## Server
- Configure the connection to periphery agents.
- Set alerting thresholds.
- Can be attached to by **Deployments**, **Stacks**, **Repos**, and **Builders**.
## [Deployment](resources/deploy-containers/index.mdx)
## Deployment
- Deploy a docker container on the attached Server.
- Manage services at the container level, perform orchestration using **Procedures** and **ResourceSyncs**.
## [Stack](resources/docker-compose)
## Stack
- Deploy with docker compose.
- Provide the compose file in UI, or move the files to a git repo and use a webhook for auto redeploy on push.
@@ -33,29 +33,29 @@ All resources which depend on git repos / docker registries are able to use thes
- Put scripts in git repos, and run them on a Server, or using a Builder.
- Can build binaries, perform automation, really whatever you can think of.
## [Build](resources/build-images/index.mdx)
## Build
- Build application source into docker images, and push them to the configured registry.
- The source can be any git repo containing a Dockerfile.
## [Builder](resources/build-images/builders)
## Builder
- Either points to a connected server, or holds configuration to launch a single-use AWS instance to build the image.
- Can be attached to **Builds** and **Repos**.
## [Procedure](resources/procedures#procedures)
## Procedure
- Compose many actions on other resource type, like `RunBuild` or `DeployStack`, and run it on button push (or with a webhook).
- Can run one or more actions in parallel "stages", and compose a series of parallel stages to run sequentially.
## [Action](resources/procedures#actions)
## Action
- Write scripts calling the Komodo API in Typescript
- Use a pre-initialized Komodo client within the script, no api keys necessary.
- Type aware in UI editor. Get suggestions and see in depth docs as you type.
- The Typescript client is also [published on NPM](https://www.npmjs.com/package/komodo_client).
## [Resource Sync](resources/sync-resources)
## ResourceSync
- Orchestrate all your configuration declaratively by defining it in `toml` files, which are checked into a git repo.
- Can deploy **Deployments** and **Stacks** if changes are suggested.
-47
View File
@@ -1,47 +0,0 @@
# Automatic Updates
Starting from **v1.19.0**, new Komodo installs will automatically create the
**Global Auto Update** [Procedure](../resources/procedures#procedures), scheduled daily.
If you don't have it, this is the Toml:
```toml
[[procedure]]
name = "Global Auto Update"
description = "Pulls and auto updates Stacks and Deployments using 'poll_for_updates' or 'auto_update'."
tags = ["system"]
config.schedule = "Every day at 03:00"
[[procedure.config.stage]]
name = "Stage 1"
enabled = true
executions = [
{ execution.type = "GlobalAutoUpdate", execution.params = {}, enabled = true }
]
```
:::info
You are also able to integrate `GlobalAutoUpdate` into other Procedures
to coordinate the timing with other processes, such as backup. There is
nothing special about this Procedure, it's just created by default for
guidance / convenience.
:::
### How does it work?
Both Stacks and Deployments allow you to configure **Poll for Updates** or **Auto Update**.
When [**GlobalAutoUpdate**](https://docs.rs/komodo_client/latest/komodo_client/api/execute/struct.GlobalAutoUpdate.html)
is run, Komodo will loop through all the resources with either of these options enabled,
and run [**PullStack**](https://docs.rs/komodo_client/latest/komodo_client/api/execute/struct.PullStack.html) / [**PullDeployment**](https://docs.rs/komodo_client/latest/komodo_client/api/execute/struct.PullDeployment.html)
in order to pick up any newer images **at the same tag**.
Note that in order to work, it requires use of a "Rolling" image tag, such as `:latest`.
:::info
If you use git sources Stacks and want to automatically update image tags, check out
[Renovate](https://github.com/renovatebot/renovate?tab=readme-ov-file#what-is-the-mend-renovate-cli)
:::
For resources with **Poll for Updates** enabled and an Alerter configured, it will
send an alert that a newer image is available, and display the update available indicator in the UI
For resource with **Auto Update** enabled, it will go ahead and Redeploy *just the services* with
newer images (by default). If an Alerter is configured, it will also send an alert that this occured.
+1 -125
View File
@@ -1,127 +1,3 @@
# Backup and Restore
:::info
Database backup and restore is actually a function of the [Komodo CLI](../ecosystem/cli),
which is packaged in with the Komodo Core image for convenience.
:::
Starting from **v1.19.0**, new Komodo installs will automatically create the
**Backup Core Database** [Procedure](../resources/procedures#procedures), scheduled daily.
If you don't have it, this is the Toml:
```toml
[[procedure]]
name = "Backup Core Database"
description = "Triggers the Core database backup at the scheduled time."
tags = ["system"]
config.schedule = "Every day at 01:00"
[[procedure.config.stage]]
name = "Stage 1"
enabled = true
executions = [
{ execution.type = "BackupCoreDatabase", execution.params = {}, enabled = true }
]
```
:::info
You are also able to integrate `BackupCoreDatabase` into other Procedures, for example to trigger
this process before launching a backup container. There is nothing special about this Procedure,
it's just created by default for guidance / convenience.
:::
## Backups
When Komodo takes a database backup, it creates a **folder named for the time the backup was taken**,
and dumps the gzip-compressed documents to files in this folder.
In order to store the backups to disk, **mount a host path to `/backups`** in the Komodo Core container.
Due to its larger size and relative unimportance, the `Stats` collection (containing historical server cpu / mem / disk usage)
is not included in dated backups. Just latest Stats are maintained at the top level of the backup folder.
In order to prevent unbounded growth, the backup process implements a pruning feature which will ensure
only the most recent 14 backup folders are kept. To change this number, set `max_backups` (`KOMODO_CLI_MAX_BACKUPS`)
in `core.config.toml`, `komodo.cli.toml`, or in the Core container environment.
```
# Folder structure
/backups
| 2025-08-12_03-00-01
| | Action.gz
| | Alerter.gz
| | ...
| 2025-08-13_03-00-01
| 2025-08-14_03-00-01
| ...
| Stats.gz
```
:::warning
Currently no encryption is supported,
so you may want to encrypt the files before backing up remotely if your backup solution doesn't support that natively.
:::
## Remote Backups
Since database backup is actually a function of the [Komodo CLI](../ecosystem/cli), you can also backup directly to
a remote server using the `ghcr.io/moghtech/komodo-cli` image. This service will backup once and then exit, so the scheduled deployment should still happen using a Procedure or Action:
```yaml
services:
cli:
image: ghcr.io/moghtech/komodo-cli
command: km database backup -y
volumes:
- /path/to/komodo/backups:/backups
environment:
## Database port must be reachable.
KOMODO_DATABASE_ADDRESS: komodo.example.com:27017
KOMODO_DATABASE_USERNAME: <db username>
KOMODO_DATABASE_PASSWORD: <db password>
KOMODO_DATABASE_DB_NAME: komodo
KOMODO_CLI_MAX_BACKUPS: 30 # set to your preference
```
## Restore
The Komodo CLI handles database restores as well.
```yaml
services:
cli:
image: ghcr.io/moghtech/komodo-cli
## Optionally specify a specific folder with `--restore-folder`,
## otherwise restores the most recent backup.
command: km database restore -y # --restore-folder 2025-08-14_03-00-01
volumes:
# Same mount to backup files as above
- /path/to/komodo/backups:/backups
environment:
## Database port must be reachable.
## Note the different env vars needed compared to backup.
## This is to prevent any accidental restores.
KOMODO_CLI_DATABASE_TARGET_ADDRESS: komodo.example.com:27017
KOMODO_CLI_DATABASE_TARGET_USERNAME: <db username>
KOMODO_CLI_DATABASE_TARGET_PASSWORD: <db password>
KOMODO_CLI_DATABASE_TARGET_DB_NAME: komodo-restore
```
:::warning
The restore process can be run multiple times with same backup files, and won't create any extra copies.
HOWEVER it will not "clear" the target database beforehand. If the restore database is already populated,
those old documents will also remain. You may want to drop / delete the target database
before restoring to it in this case.
:::
## Consistency
So long as the backup process completes successfully, the files produces can always be restored
no matter how active the Komodo instance is at the time of backup. However writes that happen during
the backup process, such as updates to the resource configuration, may or may not be included in the backup
depending on the timing.
While it should be rare that this causes any kind of issue when it comes to restoring, if your
Komodo undergoes a lot of usage at all hours and you are worried about consistency,
you could consider [locking](https://www.mongodb.com/docs/manual/reference/method/db.fsyncLock/#mongodb-method-db.fsyncLock)
Mongo before the backup. Just make sure to [unlock](https://www.mongodb.com/docs/manual/reference/method/db.fsyncUnlock/)
the database afterwards.
The [Komodo CLI](../cli)
+1 -1
View File
@@ -14,7 +14,7 @@ wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/fe
wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env
```
2. Edit the variables in `komodo/compose.env`.
3. Deploy using command:
3. Deploy:
```bash
docker compose -p komodo -f komodo/ferretdb.compose.yaml --env-file komodo/compose.env up -d
+1 -1
View File
@@ -1,4 +1,4 @@
# Setup Komodo Core
# Setup Komodo
To run Komodo, you will need Docker. See [the docker install docs](https://docs.docker.com/engine/install/).
+2 -2
View File
@@ -1,6 +1,6 @@
# MongoDB
[**MongoDB**](https://www.mongodb.com) is the standard database for Komodo.
[**MongoDB**](https://www.mongodb.com) is the traditional database for Komodo.
Komodo Core communicates with the database using the MongoDB driver.
1. Copy `komodo/mongo.compose.yaml` and `komodo/compose.env` to your host:
@@ -9,7 +9,7 @@ wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/mo
wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env
```
2. Edit the variables in `komodo/compose.env`.
3. Deploy using command:
3. Deploy:
```bash
docker compose -p komodo -f komodo/mongo.compose.yaml --env-file komodo/compose.env up -d
@@ -6,9 +6,7 @@ A variable / secret in Komodo is just a key-value pair.
KEY_1 = "value_1"
```
You can interpolate the value into any resource Environment
(and most other user configurable inputs, such as Repo `On Clone` and `On Pull`, or Stack `Extra Args`)
using double brackets around the key to trigger interpolation:
You can interpolate the value into any Environment (and most other user configurable inputs, such as Repo `On Clone` and `On Pull`, or Stack `Extra Args`) using double brackets around the key to trigger interpolation:
```toml
# Before interpolation
@@ -22,7 +20,7 @@ SOME_ENV_VAR = value_1
- **In the UI**, you can go to `Settings` page, `Variables` tab. Here, you can create some Variables to store in the Komodo database.
- There is a "secret" option you can check, this will **prevent the value from exposure in any updates / logs**, as well as prevent access to the value to any **non-admin** Komodo users.
- Variables can also be managed in ResourceSyncs (see [example](/docs/resources/sync-resources#deployments)) but should only be done for non-secret variables, to avoid committing sensitive data. You should manage secrets using one of the following options.
- Variables can also be managed in ResourceSyncs (see [example](/docs/sync-resources#deployments)) but should only be done for non-secret variables, to avoid committing sensitive data. You should manage secrets using one of the following options.
- **Mount a config file to Core**: https://komo.do/docs/setup/advanced#mount-a-config-file
- In the Komodo Core config file, you can configure `secrets` using a block like:
+36 -54
View File
@@ -1,4 +1,4 @@
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
/**
* Creating a sidebar enables you to:
@@ -13,9 +13,10 @@ import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
const sidebars: SidebarsConfig = {
docs: [
"intro",
"resources",
{
type: "category",
label: "Setup",
label: "Setup Komodo Core",
link: {
type: "doc",
id: "setup/index",
@@ -23,69 +24,50 @@ const sidebars: SidebarsConfig = {
items: [
"setup/mongo",
"setup/ferretdb",
"setup/connect-servers",
"setup/backup",
"setup/advanced",
"setup/version-upgrades",
"setup/backup",
],
},
"connect-servers",
{
type: "category",
label: "Build Images",
link: {
type: "doc",
id: "build-images/index",
},
items: [
"build-images/configuration",
"build-images/pre-build",
"build-images/builders",
"build-images/versioning",
],
},
{
type: "category",
label: "Resources",
label: "Deploy Containers",
link: {
type: "doc",
id: "resources/index",
id: "deploy-containers/index",
},
items: [
{
type: "category",
label: "Build Images",
link: {
type: "doc",
id: "resources/build-images/index",
},
items: [
"resources/build-images/configuration",
"resources/build-images/pre-build",
"resources/build-images/builders",
"resources/build-images/versioning",
],
},
{
type: "category",
label: "Deploy Containers",
link: {
type: "doc",
id: "resources/deploy-containers/index",
},
items: [
"resources/deploy-containers/configuration",
"resources/deploy-containers/lifetime-management",
],
},
"resources/docker-compose",
"resources/auto-update",
"resources/variables",
"resources/procedures",
"resources/sync-resources",
"resources/webhooks",
"resources/permissioning",
],
},
{
type: "category",
label: "Ecosystem",
link: {
type: "doc",
id: "ecosystem/index",
},
items: [
"ecosystem/cli",
"ecosystem/api",
"ecosystem/community",
"ecosystem/development",
"deploy-containers/configuration",
"deploy-containers/lifetime-management",
// "deploy-containers/choosing-builder",
// "deploy-containers/versioning",
],
},
"docker-compose",
"variables",
"procedures",
"permissioning",
"sync-resources",
"webhooks",
"api",
"cli",
"version-upgrades",
"development",
"other-resources"
],
};
+1 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.89.0 as builder
FROM rust:1.88.0 as builder
WORKDIR /builder
COPY . .
+1 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.89.0 as builder
FROM rust:1.88.0 as builder
WORKDIR /builder
COPY . .
+4 -7
View File
@@ -1,7 +1,7 @@
import * as Types from "./types.js";
export type AuthResponses = {
GetLoginOptions: Types.GetLoginOptionsResponse;
SignUpLocalUser: Types.SignUpLocalUserResponse;
CreateLocalUser: Types.CreateLocalUserResponse;
LoginLocalUser: Types.LoginLocalUserResponse;
ExchangeForJwt: Types.ExchangeForJwtResponse;
GetUser: Types.GetUserResponse;
@@ -134,10 +134,9 @@ export type ReadResponses = {
ListDockerRegistryAccounts: Types.ListDockerRegistryAccountsResponse;
};
export type WriteResponses = {
CreateLocalUser: Types.CreateLocalUserResponse;
UpdateUserUsername: Types.UpdateUserUsernameResponse;
UpdateUserPassword: Types.UpdateUserPasswordResponse;
DeleteUser: Types.DeleteUserResponse;
UpdateUserUsername: Types.UpdateUserUsername;
UpdateUserPassword: Types.UpdateUserPassword;
DeleteUser: Types.DeleteUser;
CreateServiceUser: Types.CreateServiceUserResponse;
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
@@ -310,6 +309,4 @@ export type ExecuteResponses = {
DestroyStackService: Types.Update;
TestAlerter: Types.Update;
ClearRepoCache: Types.Update;
BackupCoreDatabase: Types.Update;
GlobalAutoUpdate: Types.Update;
};
+30 -88
View File
@@ -453,8 +453,6 @@ export declare enum Operation {
CommitSync = "CommitSync",
RunSync = "RunSync",
ClearRepoCache = "ClearRepoCache",
BackupCoreDatabase = "BackupCoreDatabase",
GlobalAutoUpdate = "GlobalAutoUpdate",
CreateVariable = "CreateVariable",
UpdateVariableValue = "UpdateVariableValue",
DeleteVariable = "DeleteVariable",
@@ -995,12 +993,6 @@ export type Execution =
} | {
type: "ClearRepoCache";
params: ClearRepoCache;
} | {
type: "BackupCoreDatabase";
params: BackupCoreDatabase;
} | {
type: "GlobalAutoUpdate";
params: GlobalAutoUpdate;
} | {
type: "Sleep";
params: Sleep;
@@ -1143,6 +1135,15 @@ export interface GitProviderAccount {
token?: string;
}
export type CreateGitProviderAccountResponse = GitProviderAccount;
/** JSON containing an authentication token. */
export interface JwtResponse {
/** A token the user can use to authenticate their requests. */
jwt: string;
}
/** Response for [CreateLocalUser]. */
export type CreateLocalUserResponse = JwtResponse;
export type CreateProcedureResponse = Procedure;
export type CreateRepoWebhookResponse = NoData;
export type UserConfig =
/** User that logs in with username / password */
{
@@ -1211,9 +1212,6 @@ export interface User {
all?: Record<ResourceTarget["type"], PermissionLevelAndSpecifics | PermissionLevel>;
updated_at?: I64;
}
export type CreateLocalUserResponse = User;
export type CreateProcedureResponse = Procedure;
export type CreateRepoWebhookResponse = NoData;
export type CreateServiceUserResponse = User;
export type CreateStackWebhookResponse = NoData;
export type CreateSyncWebhookResponse = NoData;
@@ -1429,13 +1427,6 @@ export interface DeploymentQuerySpecifics {
update_available?: boolean;
}
export type DeploymentQuery = ResourceQuery<DeploymentQuerySpecifics>;
/** JSON containing an authentication token. */
export interface JwtResponse {
/** User ID for signed in user. */
user_id: string;
/** A token the user can use to authenticate their requests. */
jwt: string;
}
/** Response for [ExchangeForJwt]. */
export type ExchangeForJwtResponse = JwtResponse;
/** Response containing pretty formatted toml contents. */
@@ -2075,11 +2066,6 @@ export interface ServerConfig {
* Default: http://localhost:8120
*/
address: string;
/**
* The address to use with links for containers on the server.
* If empty, will use the 'address' for links.
*/
external_address?: string;
/** An optional region label */
region?: string;
/**
@@ -2567,6 +2553,11 @@ export interface Tag {
owner?: string;
/** Hex color code with alpha for UI display */
color?: TagColor;
/**
* This field is not stored on database,
* but rather populated at query time based on results from the other resources.
*/
unused?: boolean;
}
export type GetTagResponse = Tag;
export type GetUpdateResponse = Update;
@@ -3716,11 +3707,6 @@ export interface ServerListItemInfo {
region: string;
/** Address of the server. */
address: string;
/**
* External address of the server (reachable by users).
* Used with links.
*/
external_address?: string;
/** The Komodo Periphery version of the server. */
version: string;
/** Whether server is configured to send unreachable alerts. */
@@ -3894,8 +3880,6 @@ export interface ServerQuerySpecifics {
/** Server-specific query */
export type ServerQuery = ResourceQuery<ServerQuerySpecifics>;
export type SetLastSeenUpdateResponse = NoData;
/** Response for [SignUpLocalUser]. */
export type SignUpLocalUserResponse = JwtResponse;
export interface StackQuerySpecifics {
/**
* Query only for Stacks on these Servers.
@@ -4000,16 +3984,15 @@ export interface AwsBuilderConfig {
secrets?: string[];
}
/**
* Backs up the Komodo Core database to compressed jsonl files.
* Admin only. Response: [Update]
* Backs up the database to compressed jsonl files. Admin only.
*
* Mount a folder to `/backups`, and Core will use it to create
* timestamped database dumps, which can be restored using
* the Komodo CLI.
* `ghcr.io/moghtech/komodo-util`.
*
* TODO: Link to docs
*/
export interface BackupCoreDatabase {
export interface BackupDatabase {
}
/** Builds multiple Repos in parallel that match pattern. Response: [BatchExecutionResponse]. */
export interface BatchBuildRepo {
@@ -4247,10 +4230,7 @@ export interface CancelRepoBuild {
/** Can be id or name */
repo: string;
}
/**
* Clears all repos from the Core repo cache. Admin only.
* Response: [Update]
*/
/** Clears all repos from the Core repo cache. Admin only. */
export interface ClearRepoCache {
}
/**
@@ -4752,17 +4732,19 @@ export interface CreateGitProviderAccount {
account: _PartialGitProviderAccount;
}
/**
* **Admin only.** Create a local user.
* Response: [User].
* Create a new local user account. Will fail if a user with the
* given username already exists.
* Response: [CreateLocalUserResponse].
*
* Note. Not to be confused with /auth/SignUpLocalUser.
* This method requires admin user credentials, and can
* bypass disabled user registration.
* Note. This method is only available if the core api has `local_auth` enabled.
*/
export interface CreateLocalUser {
/** The username for the local user. */
/** The username for the new user. */
username: string;
/** A password for the local user. */
/**
* The password for the new user.
* This cannot be retreived later.
*/
password: string;
}
/**
@@ -4869,8 +4851,6 @@ export interface CreateSyncWebhook {
export interface CreateTag {
/** The name of the tag. */
name: string;
/** Tag color. Default: Slate. */
color?: TagColor;
}
/**
* Configures the behavior of [CreateTerminal] if the
@@ -6138,16 +6118,6 @@ export interface GetVersionResponse {
/** The version of the core api. */
version: string;
}
/**
* Trigger a global poll for image updateson Stacks and Deployments
* with `poll_for_updates` or `auto_update` enabled.
* Admin only. Response: [Update]
*
* 1. `docker compose pull` any Stacks / Deployments with `poll_for_updates` or `auto_update` enabled. This will pick up any available updates.
* 2. Redeploy Stacks / Deployments that have updates found.
*/
export interface GlobalAutoUpdate {
}
/**
* Inspect the docker container associated with the Deployment.
* Response: [Container].
@@ -7141,9 +7111,7 @@ export interface RunAction {
/** Id or name */
action: string;
/**
* Custom arguments which are merged on top of the default arguments.
* CLI Format: `"VAR1=val1&VAR2=val2"`
*
* Custom arguments which can override the defaults.
* Webhook-triggered actions use this to pass WEBHOOK_BRANCH and WEBHOOK_BODY.
*/
args?: JsonObject;
@@ -7312,23 +7280,6 @@ export interface SetUsersInUserGroup {
/** The user ids or usernames to hard set as the group's users. */
users: string[];
}
/**
* Sign up a new local user account. Will fail if a user with the
* given username already exists.
* Response: [SignUpLocalUserResponse].
*
* Note. This method is only available if the core api has `local_auth` enabled,
* and if user registration is not disabled (after the first user).
*/
export interface SignUpLocalUser {
/** The username for the new user. */
username: string;
/**
* The password for the new user.
* This cannot be retreived later.
*/
password: string;
}
/** Info for network interface usage. */
export interface SingleNetworkInterfaceUsage {
/** The network interface name */
@@ -7843,8 +7794,8 @@ export type AuthRequest = {
type: "GetLoginOptions";
params: GetLoginOptions;
} | {
type: "SignUpLocalUser";
params: SignUpLocalUser;
type: "CreateLocalUser";
params: CreateLocalUser;
} | {
type: "LoginLocalUser";
params: LoginLocalUser;
@@ -8048,12 +7999,6 @@ export type ExecuteRequest = {
} | {
type: "ClearRepoCache";
params: ClearRepoCache;
} | {
type: "BackupCoreDatabase";
params: BackupCoreDatabase;
} | {
type: "GlobalAutoUpdate";
params: GlobalAutoUpdate;
};
/**
* One representative IANA zone for each distinct base UTC offset in the tz database.
@@ -8562,9 +8507,6 @@ export type UserRequest = {
params: DeleteApiKey;
};
export type WriteRequest = {
type: "CreateLocalUser";
params: CreateLocalUser;
} | {
type: "UpdateUserUsername";
params: UpdateUserUsername;
} | {
-2
View File
@@ -167,8 +167,6 @@ export var Operation;
Operation["CommitSync"] = "CommitSync";
Operation["RunSync"] = "RunSync";
Operation["ClearRepoCache"] = "ClearRepoCache";
Operation["BackupCoreDatabase"] = "BackupCoreDatabase";
Operation["GlobalAutoUpdate"] = "GlobalAutoUpdate";
Operation["CreateVariable"] = "CreateVariable";
Operation["UpdateVariableValue"] = "UpdateVariableValue";
Operation["DeleteVariable"] = "DeleteVariable";
+89
View File
@@ -0,0 +1,89 @@
import { useRead } from "@lib/hooks";
import { Button } from "@ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@ui/dropdown-menu";
import { AlertTriangle } from "lucide-react";
import { AlertLevel } from ".";
import { ResourceLink } from "@components/resources/common";
import { UsableResource } from "@types";
import { Dialog } from "@ui/dialog";
import { Types } from "komodo_client";
import { useState } from "react";
import { AlertDetailsDialogContent } from "./details";
export const TopbarAlerts = () => {
const { data } = useRead(
"ListAlerts",
{ query: { resolved: false } },
{ refetchInterval: 3_000 }
);
const [open, setOpen] = useState(false);
// If this is set, details will open.
const [alert, setAlert] = useState<Types.Alert>();
if (!data || data.alerts.length === 0) {
return null;
}
return (
<>
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild disabled={!data?.alerts.length}>
<Button variant="ghost" size="icon" className="relative">
<AlertTriangle className="w-4 h-4" />
{!!data?.alerts.length && (
<div className="absolute top-0 right-0 w-4 h-4 bg-red-500 flex items-center justify-center text-[10px] text-white rounded-full">
{data.alerts.length}
</div>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={20}>
{data?.alerts.map((alert) => (
<DropdownMenuItem
key={alert._id?.$oid}
className="flex items-center gap-8 border-b last:border-none cursor-pointer"
onClick={() => setAlert(alert)}
>
<div className="w-24">
<AlertLevel level={alert.level} />
</div>
<div className="w-64">
<div className="w-fit">
<ResourceLink
type={alert.target.type as UsableResource}
id={alert.target.id}
onClick={() => setOpen(false)}
/>
</div>
</div>
<p className="w-64">{alert.data.type}</p>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<AlertDetails alert={alert} onClose={() => setAlert(undefined)} />
</>
);
};
const AlertDetails = ({
alert,
onClose,
}: {
alert: Types.Alert | undefined;
onClose: () => void;
}) => (
<>
{alert && (
<Dialog open={!!alert} onOpenChange={(o) => !o && onClose()}>
<AlertDetailsDialogContent alert={alert} onClose={onClose} />
</Dialog>
)}
</>
);
+1 -5
View File
@@ -211,14 +211,12 @@ export const NewLayout = ({
enabled,
onConfirm,
onOpenChange,
configureLabel = "a unique name",
}: {
entityType: string;
children: ReactNode;
enabled: boolean;
onConfirm: () => Promise<unknown>;
onOpenChange?: (open: boolean) => void;
configureLabel?: string;
}) => {
const [open, set] = useState(false);
const [loading, setLoading] = useState(false);
@@ -238,9 +236,7 @@ export const NewLayout = ({
<DialogContent>
<DialogHeader>
<DialogTitle>New {entityType}</DialogTitle>
<DialogDescription>
Enter {configureLabel} for the new {entityType}.
</DialogDescription>
<DialogDescription>Enter a unique name for the new {entityType}.</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-6 py-8">{children}</div>
<DialogFooter>
@@ -133,14 +133,11 @@ export const ActionConfig = ({ id }: { id: string }) => {
</div>
</SelectTrigger>
<SelectContent>
{Object.values(Types.FileFormat)
// Don't allow selection of Toml, as this option will break resource sync
.filter((f) => f !== Types.FileFormat.Toml)
.map((format) => (
<SelectItem value={format}>
{snake_case_to_upper_space_case(format)}
</SelectItem>
))}
{Object.values(Types.FileFormat).map((format) => (
<SelectItem value={format}>
{snake_case_to_upper_space_case(format)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

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