Compare commits

..
583 Commits
Author SHA1 Message Date
mbecker20 15eaeab68d deploy 2.0.0-dev-120 2026-02-26 15:06:15 -08:00
mbecker20 89ea5ad5a4 stack and deployment state color include update available. Ensure server version mismatch color applied everywhere 2026-02-26 15:02:37 -08:00
mbecker20 c05dde3678 add close button to update / alert details drawer 2026-02-26 14:43:24 -08:00
mbecker20 a17a1005c7 smaller more consistent gaps 2026-02-26 14:38:36 -08:00
mbecker20 d13314eb98 stack stopped and deployment exited warning instead of critical 2026-02-26 14:25:31 -08:00
mbecker20 f41a61116a fix batch executions width 2026-02-26 14:24:58 -08:00
mbecker20 13fbcae105 edit swarm join command 2026-02-26 14:09:01 -08:00
mbecker20 71c437962f ensure section headers consistent spacing 2026-02-26 14:06:57 -08:00
mbecker20 e4147ccdaf rename ConfigSwitch onChange -> onCheckedChange 2026-02-26 13:41:51 -08:00
mbecker20 5089375211 improve tab styling 2026-02-26 13:38:07 -08:00
mbecker20 e489812af4 Fix config input issue 2026-02-26 13:37:32 -08:00
mbecker20 7373ff8d0e execution buttons disabled when loading 2026-02-26 12:53:58 -08:00
mbecker20 4e1cd32f3f Select Template 2026-02-25 19:31:28 -08:00
mbecker20 57211746f1 hide actions when none 2026-02-25 19:21:48 -08:00
mbecker20 5e153fb02b dashboard active styling 2026-02-25 19:21:22 -08:00
mbecker20 b28a413005 procedure / action webhook branch mobile style 2026-02-25 17:46:22 -08:00
mbecker20 691671abbd Load Average is first historical stat 2026-02-25 17:43:54 -08:00
mbecker20 504e81c2f7 improve login no auth configured and passkey pending 2026-02-25 17:36:02 -08:00
mbecker20 0a479a0f4a bump chef rust version 2026-02-25 16:08:55 -08:00
mbecker20 6a05779ceb deploy 2.0.0-dev-118 2026-02-25 16:07:05 -08:00
mbecker20 acd27ba058 bump rust version to 1.93.1 and dep versions 2026-02-25 16:06:37 -08:00
Maxwell BeckerandGitHub 96c4ae9fc5 2.0.0 UI (#1220)
* new ui using mantine

* resources page

* prog on resource page

* resources and resource layouts

* confirm button and modal

* tweaks

* update details

* topbar updates

* add skeletons for resource implementations

* add resource tables

* add tags to recents cards

* resource page table scrolling

* table component + tags filter

* export toml

* New Resource button

* Fix update details capture closing

* tweaks

* omni search

* refine config

* config tweaks

* implement more configs / resource selector

* add profile page

* provider / account selectors

* container table page

* build config

* deployment config

* fix deployment build version selector

* fix secrets selector

* resource sync config

* mobile topbar and updates

* update details fz sm

* stack config

* terminals page

* create terminal in prog

* create terminal menu

* finish create terminal menu

* terminal pages working

* stack tabs / info

* add executions

* add server header info

* confirm pubkey modal

* improve resource header styling

* FileSource component

* stack service table, move icons.ts

* basic procedure config

* tweak procedure config

* container / image pages

* network / volume pages

* clean up docker resource pages

*  basic log / terminal ui

* reusable log section

* styling

* clean up resource components

* delete in resource header

* log auto select stderr

* fix some bgs

* stack logs with service selector

* stack terminals

* add deployment executions

* use correct icon

* useResource hooks

* build info

* build info

* tweaks

* server tabs

* fix terminal section target

* prog on server tabs

* server stats

* light theme

* start on historical stats

* stack service page

* resource sync tabs

* sync tabs

* more topbar icons

* add settings basic

* add topbar alerts

* tweak stream selector behavior

* tweak alert icon topbar

* improve styling smaller screen

* schedules page and other progress

* onboarding keys

* improve schedule page descriptions

* improve update notifications

* schedule timezone selector

* tag color selector

* finish settings / providers

* use shared-text-update component so settings tables aren't janky

* updates page

* refine updates page

* alert page

* standardize borders

* theme and swarm

* swarm tabs

* swarm node page

* swarm config page

* swarm pages

* swarm task and secret pages

* swarm stack page

* fix stack log service selector in swarm mode

* standard inspect section

* swarm inspect tab

* server and swarm resources tab

* add disable confirm dialog (modal) option for executions

* stack update available indicator

* deployment update available

* add template switch to resource headers

* ResourceHeader + rename

* set editing name onclick

* repo tabs

* server stats table

* refine a bit

* refine deployment / stack header info

* show server stats dashboard. dashboard tables

* action last run in config

* SettingsUsers page

* user page etc

* manage api key

* user base permissions

* color the table multi select

* user group page

* UserAddUserGroup

* active includes deployments / stacks

* improve small screen view

* fix docker pages execution showing

* clean up

* rename frontend to UI

* align profile page styling

* config maintenance windows

* finish maintenance windows

* builder config

* add batch execute dropdown / confirm menu

* batch execute styling

* deploy 2.0.0-dev-117

* improve stats card light theme

* add update page

* improve mobile

* terminal group nowrap

* mobile improvements

* allow unused again

* improve mobile font sizing

* improve mobile updates / alerts

* mobile tabs

* alert page

* add server version mismatch color

* new resource, clearable selector

* Fix build show info tab

* copy resources

* keyboard shortcuts

* server resource header version mismatch

* fix type errors

* container page server multi select

* confirm button clear timeout

* hash compare force uses first 8 for short hash

* fix log height

* copy webhooks

* responsive tweaks

* add icons to server stat sections

* add historical server stats charts

* server stat current card shows usage numbers

* refine current stats more

* fix shortcuts interfering with monaco brave

* clean up unused

* remove v1 frontend
2026-02-25 15:28:23 -08:00
mbecker20 5c99958cba only send auto updated alert after verifying the deploy was successful 2026-02-15 18:38:01 -08:00
mbecker20 7288f067c5 deploy 2.0.0-dev-116 2026-02-11 10:20:39 -08:00
mbecker20 5d0f7de9fb Stack check for update prefers check against deployed service image 2026-02-11 10:20:09 -08:00
mbecker20 4129abcfd0 add new config value to example config 2026-01-27 23:26:44 -08:00
mbecker20 4275e903eb deploy 2.0.0-dev-115 2026-01-27 23:19:49 -08:00
mbecker20 c57c321cbb configure session allow cross site 2026-01-27 23:19:20 -08:00
mbecker20 9a7d49b35e bump mogh server and auth 2026-01-27 22:51:05 -08:00
mbecker20 d6aaef24cd bump rust to 1.93.0 2026-01-27 13:42:20 -08:00
mbecker20 3dce84c1f5 deploy 2.0.0-dev-114 2026-01-27 12:58:57 -08:00
mbecker20 8b16f97caf check update available against all digests for image 2026-01-27 12:58:32 -08:00
mbecker20 c98a809db4 serve static ui index.html with ETag / no-cache 2026-01-25 12:36:40 -08:00
mbecker20 c066b4fe16 mogh_server = "1.2.0" 2026-01-25 11:26:17 -08:00
mbecker20 b54fa235bc dev-113 2026-01-24 12:48:38 -08:00
mbecker20 05fd1aba2c batch check for updates, add check to table multi select actions 2026-01-24 12:48:13 -08:00
mbecker20 1a5290e124 cleanup 2026-01-24 11:33:49 -08:00
mbecker20 538e11ef3b dev 112 2026-01-24 11:33:22 -08:00
mbecker20 9208e9bf86 skip auto update for images with pinned digest 2026-01-24 11:30:41 -08:00
mbecker20 31640e3df8 fix require to import 2026-01-24 01:06:15 -08:00
mbecker20 42751d1b90 fix tailwind config module 2026-01-24 01:04:31 -08:00
mbecker20 673ab5c17d bump node version in all the dockerfiles 2026-01-24 01:01:05 -08:00
mbecker20 ad00f38758 bump frontend node version 2026-01-24 00:59:42 -08:00
mbecker20 b2459b5a64 align dashboard icon with sidebar 2026-01-24 00:58:23 -08:00
mbecker20 5923dd0e22 use mogh auth for passkey conversion 2026-01-24 00:45:32 -08:00
mbecker20 51294edd73 bump ts types and fix SwarmConfig type name conflict 2026-01-23 20:26:43 -08:00
mbecker20 baf77bf701 add api docs link 2026-01-23 14:59:22 -08:00
mbecker20 ddd99dc85d deploy 2.0.0-dev-111 2026-01-23 14:39:36 -08:00
mbecker20 a4c902282d improve swarm service / stack state inference 2026-01-23 14:39:00 -08:00
mbecker20 d7a2d4aadc clean up dangling action api keys on startup 2026-01-23 11:37:50 -08:00
mbecker20 c930468811 fix Action import from terminal 2026-01-23 03:11:47 -08:00
mbecker20 27efc81451 stack level terminal page 2026-01-23 03:07:57 -08:00
mbecker20 e44bd324f7 finish version upgrades page, and use prism theme oneLight and oneDark 2026-01-23 02:10:16 -08:00
mbecker20 b64c7e7289 deploy 2.0.0-dev-110 2026-01-23 01:10:32 -08:00
mbecker20 5c1a17fc91 improve config styling a bit more 2026-01-23 01:10:06 -08:00
mbecker20 50c3d77aed improve config quick links styling 2026-01-23 00:50:51 -08:00
mbecker20 bba1e3ae0c typegen 2026-01-23 00:24:27 -08:00
mbecker20 fc26fede66 add back legacy Action "exec" method calling convention 2026-01-23 00:23:54 -08:00
mbecker20 9cee723a1a deploy 2.0.0-dev-109 2026-01-22 19:12:55 -08:00
mbecker20 b922e86d29 deploy 2.0.0-dev-108 2026-01-22 18:38:12 -08:00
mbecker20 85dd3c0fe9 cache latest image digests and use this for image update alerting / auto update 2026-01-22 18:37:43 -08:00
mbecker20 4e3ecf7e6c clean up 2026-01-21 03:38:48 -08:00
mbecker20 18ffe635c8 remove user api, mogh auth handles api keys 2026-01-21 02:24:21 -08:00
mbecker20 8758c9ff8b gen types 2026-01-20 03:08:43 -08:00
mbecker20 5da48ece82 fmt and bump mogh auth 2026-01-20 02:58:45 -08:00
mbecker20 9a48edc43f add the write openapi definitions 2026-01-19 23:49:12 -08:00
mbecker20 93430f6dc3 more write openapi 2026-01-19 23:34:18 -08:00
mbecker20 d893ae9241 most of write openapi 2026-01-19 23:02:48 -08:00
mbecker20 24ed3cfcb1 server / stack exec openapi 2026-01-19 22:18:41 -08:00
mbecker20 36ee7ed72e most of execute openapi 2026-01-19 22:10:16 -08:00
mbecker20 5895fb843e modularize openapi structs 2026-01-19 20:50:08 -08:00
mbecker20 98f53020fb add most basic read apis 2026-01-19 19:33:18 -08:00
mbecker20 1d10873e77 improve toml comment 2026-01-19 18:03:36 -08:00
mbecker20 f5ee42029f review / fix some openapi definitions 2026-01-19 17:57:59 -08:00
mbecker20 525402f6f2 add openapi definitions to most read apis 2026-01-19 17:42:04 -08:00
mbecker20 9f877cc188 move openapi definition into client lib under feature gate 2026-01-19 15:41:38 -08:00
mbecker20 69d6b8e6ec remove the patch 2026-01-19 01:24:50 -08:00
mbecker20 526bcc1907 deploy 2.0.0-dev-107 2026-01-19 01:22:59 -08:00
mbecker20 f7badf5579 define set logging module targets so log level becomes useful 2026-01-19 01:18:50 -08:00
mbecker20 86fd7a155d try deno npm: style dep 2026-01-19 00:25:11 -08:00
mbecker20 628afc3baa deploy 2.0.0-dev-106 2026-01-18 23:37:19 -08:00
mbecker20 04f944fcc2 use full mogh dep names 2026-01-18 23:36:52 -08:00
mbecker20 f39447b513 move to mogh_resolver 2026-01-18 22:56:13 -08:00
mbecker20 612acd02ad remove derive_variants, strum's "EnumDiscriminants" covers it 2026-01-18 22:30:54 -08:00
mbecker20 90557ae514 fix cors options 2026-01-18 19:35:21 -08:00
mbecker20 0c292f7157 deploy 2.0.0-dev-105 2026-01-18 18:16:32 -08:00
mbecker20 673ee05fe9 tmp back to 104 2026-01-18 18:16:30 -08:00
mbecker20 94b5bd6660 use public ts mogh auth client 2026-01-18 16:52:49 -08:00
mbecker20 be40110004 use mogh_auth embedded api 2026-01-18 16:31:45 -08:00
mbecker20 2d4a34b820 move to mogh deps 2026-01-17 23:22:58 -08:00
mbecker20 2feb08725b fix_ws_address adds 'ws://' to direct IPs instead of 'wss://' which won't work 2026-01-06 10:14:10 -08:00
mbecker20 161393805f refactor openapi -> utoipa feature 2026-01-05 12:45:52 -08:00
mbecker20 f4b692080d remove user_id from jwt response -- its encoded transparently in jwt 2026-01-05 12:38:17 -08:00
mbecker20 e1c6c866a2 docs.html 2025-12-30 14:33:34 -08:00
mbecker20 7106328390 add authentication to openapi docs 2025-12-30 14:32:29 -08:00
mbecker20 338acba5c6 start utoipa openapi support 2025-12-30 13:02:27 -08:00
mbecker20 dcf6f2431e tweak 2025-12-29 22:31:19 -08:00
mbecker20 58b520e651 improve OIDC client init to only as needed 2025-12-29 11:22:49 -08:00
mbecker20 8be0846733 bump axum-extra 2025-12-28 10:15:00 -08:00
mbecker20 5b651f2527 tweak 2025-12-27 15:11:13 -08:00
mbecker20 026ba4fca8 use User constructor 2025-12-27 02:14:16 -08:00
mbecker20 4bca07d4ba update deps 2025-12-27 01:47:54 -08:00
mbecker20 9091f14711 update password refetch user 2025-12-26 02:07:44 -08:00
mbecker20 8b78bb838e fix alert error messages - reference correct alerter type 2025-12-26 01:57:23 -08:00
mbecker20 d384f7ca78 update to v2 docs 2025-12-24 00:46:07 -08:00
mbecker20 48475b55d5 Periphery GetLatestImageDigest + RollbackSwarmService 2025-12-21 00:58:57 -08:00
mbecker20 64d7959c75 bump deps 2025-12-21 00:57:46 -08:00
mbecker20 d1856c2325 deploy 2.0.0-dev-104 2025-12-20 12:30:03 -08:00
mbecker20 61b7fcdb0a onboarding key fix_existing_servers / priviledged 2025-12-20 12:29:24 -08:00
mbecker20 2693f13829 deploy 2.0.0-dev-103 2025-12-19 12:22:51 -08:00
mbecker20 7fea06861b improve swarm node page 2025-12-19 12:22:20 -08:00
mbecker20 abd155193a join swarm command 2025-12-19 12:10:25 -08:00
mbecker20 7a4de0343d config / secret has service and task tables 2025-12-18 15:30:37 -08:00
mbecker20 c638e3876e deploy 2.0.0-dev-102 2025-12-18 14:52:04 -08:00
mbecker20 fc4ea40b9c task links to configs / secrets 2025-12-18 13:43:16 -08:00
mbecker20 62b84328d8 link attached configs / secrets in status 2025-12-18 13:29:38 -08:00
mbecker20 a5ee0de394 tweaks 2025-12-18 02:32:08 -08:00
mbecker20 bf0ab26d22 improve swarm deployments 2025-12-18 01:36:31 -08:00
mbecker20 0dd2ade574 fix config / secret rotation 2025-12-17 12:59:08 -08:00
mbecker20 f0172c2721 move inspect to swarm tabs top level 2025-12-15 22:55:37 -08:00
mbecker20 363d330868 similar but inherently limited swarm secret management 2025-12-15 22:49:45 -08:00
mbecker20 2843711796 swarm config create / edit / delete 2025-12-15 17:04:03 -08:00
mbecker20 4f044ccde8 Service aware config rotation 2025-12-14 14:42:53 -08:00
mbecker20 9be21b54bd fix deployment / stack toml when swarm attached 2025-12-14 00:06:12 -08:00
mbecker20 057ecbcfef add load passkey frontend indicator screen 2025-12-13 23:43:13 -08:00
mbecker20 e91d63f262 deploy 2.0.0-dev-101 2025-12-13 18:37:39 -08:00
mbecker20 3b799edb30 improve the swarm tables 2025-12-13 18:37:14 -08:00
mbecker20 41c7faf48d add resources tab to swarms 2025-12-13 11:21:24 -08:00
mbecker20 e1d745aee0 status colors for swarm resources 2025-12-13 11:12:26 -08:00
mbecker20 01df3add41 Fix task state check 2025-12-13 01:01:22 -08:00
mbecker20 577973841b better api / onboarding key shape 2025-12-12 23:59:42 -08:00
mbecker20 3e5311f413 user profile mobile 2025-12-12 18:30:00 -08:00
mbecker20 46e99783f2 deploy 2.0.0-dev-100 2025-12-12 18:00:28 -08:00
mbecker20 a6590804a7 skip 2fa for third party logins 2025-12-12 18:00:03 -08:00
mbecker20 bd752b7c72 deploy 2.0.0-dev-99 2025-12-12 17:08:45 -08:00
mbecker20 de83b3a5a0 user link multiple primary login methods 2025-12-12 17:08:03 -08:00
mbecker20 c91963fdbb fix table_borders in cli config 2025-12-11 01:03:43 -08:00
mbecker20andMaxwell Becker 973aae82fd improve 2fa ux 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker c3626e0949 cli support clearing users 2fa methods 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 5ba96a58e4 binaries use bookworm 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker c6a1cf2867 deploy 2.0.0-dev-98 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker bd39aec1fd support passkey 2fa 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 079895a64d fix imports in permissions module 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 5af1c646a5 deploy 2.0.0-dev-97 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 747e03b5c4 Fix incorrect permissions query due to resource RBAC update query filter. Align logic with Alert resource RBAC filter. Re #1011 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 78c6761eca add swarm specific permission config 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker a949891d26 apply login credential lock (for demo users) to 2FA 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 9d256e420a improve totp enrollment dialog closing, fix redirect away from /login when using two factor 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker b460504b5a deploy 2.0.0-dev-96 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker e60316f2c1 TOTP 2FA with third party logins 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker c45e3eab16 deploy 2.0.0-dev-95 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker ec12f63272 double recovery code length 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker f2f77b4eb6 totp unenroll 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 677a65f8b0 local login TOTP 2fa support 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker a5a1090177 deploy 2.0.0-dev-94 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker ccb18fc88a fix server auto rotate keys default for sync 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 0ca583fe5e deploy Deployments to swarms 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 4013cd3f1e dev-93 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 5854a7de99 Deployment as swarm service 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 8c52232852 swarm stack deploy and destroy 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker fb9e5b1860 ts types 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker a1c00a8d32 implement the swarm resource associations for stack / deployment targeting swarm 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 775f5bf703 more stack / deployment deploy on swarm 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker effe737ffe periphery swarm stack deploy 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 436959cbcd docker service create 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 9957287443 remove buttons for swarm resources 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker c77de8683c swarm stack logs and Remove 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 087bcb7044 improve swarm stack page 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 0dfcaaf06a add swarm remove executions to procedure UI excluded types 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 6624a821a5 deploy 2.0.0-dev-92 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker c32af84e02 remove swarm entities apis 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 9d9add7b34 Uppercase server docker ops logs 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker db099dbe1e improve config with tall linkto sidebars 2025-12-10 21:39:56 -08:00
mbecker20andMaxwell Becker 413a8abc84 default rate limit attempts to 5 per 15 sec 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 36243e83c1 KL-4 must fallback to axum extracted IP for cases not using reverse proxy 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 076113a1de KL-4: Align log consistency 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker c998edaa73 KL-1: core_allowed_origins example config should be empty 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 4de32b08e5 KL-8 modify action state internal behavior comments 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 262999c58f KL-7 Improve typescript safety: disable allow any 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker be57f52e6e KL-6 Improve error handling in startup paths 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 3591789316 KL-6 Improve alert cache error handling 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 83f2bd65fe KL-6 Remove monitoring_interval panic 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 5e5cdb81f8 KL-5 ext: Tighten the skew tolerance re https://curity.io/resources/learn/jwt-best-practices/?utm_source=chatgpt.com#9-dealing-with-time-based-claims 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 721038a1df KL-5 JWT clock skew tolerance 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 7de98ad519 cargo clippy and bump to rust 1.91.1 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 8c62f2b5c5 KL-4 ext 2: Improve rate limiting / attempt state conveyance with response 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 85787781ee KL-4 ext: Remove brute-force compromising credential failure reasons to improve auth rate limiter effectiveness 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 25e2d4e926 KL-4 Authentication rate limiting 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker b9b8d45cbc KL-2/3 Input validation for local auth, service users, api keys, and variables 2025-12-10 21:34:30 -08:00
mbecker20andMaxwell Becker 2f2e863dbf KL-1 Configurable CORS support 2025-12-10 21:34:30 -08:00
mbecker20 a3a01f1625 add schemas for swarm commands 2025-11-27 23:47:22 -08:00
mbecker20 aec8fa2bf1 task view page with logs 2025-11-23 23:45:28 -08:00
mbecker20 7ff2dba82f swarm service page with logs 2025-11-23 12:48:23 -08:00
mbecker20 9c86b2d239 swarm service logs api 2025-11-23 00:43:59 -08:00
mbecker20 b1fec7c663 docker swarm stack read apis 2025-11-22 16:05:51 -08:00
mbecker20 8341c6b802 sward resources use list items 2025-11-22 02:41:49 -08:00
mbecker20 73285c4374 move swarm to list item + inspect on pages 2025-11-21 14:34:20 -08:00
mbecker20 32d48cdb02 swarm configs view 2025-11-21 13:21:14 -08:00
mbecker20 8e081fd09c swarm config apis 2025-11-21 12:55:37 -08:00
mbecker20 04531f1dea Explore swarm info 2025-11-20 13:00:29 -08:00
mbecker20 80f439d472 basic swarm 2025-11-19 18:02:50 -08:00
mbecker20 d5e03d6d16 prog on swarm 2025-11-19 01:48:43 -08:00
mbecker20 a9f55bb8e6 fix Terminals permissions when no perm on Server 2025-11-17 15:44:14 -08:00
mbecker20 9e765f93f5 bump deps 2025-11-17 15:43:56 -08:00
mbecker20 b3aa8e906f add context to onboarding login error and error messages sent over the communication websocket in general 2025-11-17 13:23:44 -08:00
mbecker20 03fe442aa0 in prog 2025-11-17 12:59:08 -08:00
ChanningHeandGitHub d268009a6a validate compose_cmd_wrapper for required placeholder and add interpolation support (#977) 2025-11-12 10:09:15 -08:00
mbecker20 f0697e812a shift + N open new variable dialog 2025-11-11 14:22:56 -08:00
mbecker20 78766463d6 create variable Enter submit 2025-11-11 14:18:28 -08:00
mbecker20 0fa1edba2c deploy 2.0.0-dev-90 2025-11-11 14:13:55 -08:00
mbecker20 bbd968cac3 bump toml pretty with fix syncing procedure executions with multiline batch patterns 2025-11-11 14:13:25 -08:00
mbecker20 5f24fc1be3 deploy 2.0.0-dev-89 2025-11-11 00:44:49 -08:00
mbecker20 7ecd2b0b0b improve cmd wrapper with comment removal support 2025-11-11 00:43:54 -08:00
mbecker20 7bf44d2e04 fix some broken tabs 2025-11-11 00:35:24 -08:00
mbecker20 24e0672384 dashboard resets page title 2025-11-11 00:18:16 -08:00
mbecker20 04f081631f deploy 2.0.0-dev-88 2025-11-11 00:16:07 -08:00
mbecker20 b1af956b63 fix dashboard pie chart code splitting issue 2025-11-11 00:05:32 -08:00
mbecker20 370712b29f gen served client types 2025-11-11 00:05:02 -08:00
mbecker20 2b6c552964 canius lite update 2025-11-11 00:04:52 -08:00
mbecker20 434a1d8ea9 clippy lint 2025-11-11 00:04:39 -08:00
ChanningHeandGitHub 0b7f28360f Add optional command wrapper for Docker Compose in StackConfig (#973) 2025-11-10 23:59:09 -08:00
ChanningHeandGitHub 3c8ef0ab29 Add track option for Additional Env Files (#955) 2025-11-10 23:47:07 -08:00
mbecker20 930b2423c3 deploy 2.0.0-dev-87 2025-11-07 10:33:23 -08:00
mbecker20 546747b5f2 add timeout to dns ip resolve, only use ipv4 2025-11-07 10:32:55 -08:00
mbecker20 c6df866755 better aws builder config organization 2025-11-07 10:04:45 -08:00
mbecker20 ea5e684915 better useUserTargetPermissions 2025-11-06 22:18:31 -08:00
mbecker20 64db8933de RefreshBuildCache after build 2025-11-04 00:27:34 -08:00
mbecker20 7a5580de57 builder uppercase login 2025-11-04 00:06:46 -08:00
mbecker20 b1656bb174 log about enabling user linger 2025-11-03 10:29:50 -08:00
Badal SinghandGitHub 559ce103da Update setup-periphery.py (#958) 2025-11-03 09:57:49 -08:00
mbecker20 75e278a57b builder fix partial_default 2025-10-30 00:41:27 -07:00
mbecker20 430f3ddc34 fix omni search container double select on same name 2025-10-29 00:02:32 -07:00
mbecker20 6c30c202e9 add Terminals to omni search 2025-10-28 23:59:41 -07:00
mbecker20 c5401de1c5 tweak user level tab view 2025-10-28 11:42:29 -07:00
mbecker20 7a3d9e0ef6 tweak description 2025-10-28 00:32:39 -07:00
mbecker20 595e3ece42 deploy 2.0.0-dev-86 2025-10-27 21:05:13 -07:00
mbecker20 a3bc895755 fix terminal disconnect 2025-10-27 21:04:46 -07:00
mbecker20 3e3def03ec terminal init properly lexes init command 2025-10-27 21:01:15 -07:00
mbecker20 bc672d9649 deploy 2.0.0-dev-85 2025-10-27 20:01:18 -07:00
mbecker20 ea6dee4d51 clippy lint 2025-10-27 19:13:43 -07:00
mbecker20 b985f18c74 deploy 2.0.0-dev-84 2025-10-27 19:12:54 -07:00
mbecker20 45909b2f04 pid1 reaper doesn't work, init: true should be required in compose 2025-10-27 19:06:50 -07:00
mbecker20 2b5a54ce89 deploy 2.0.0-dev-83 2025-10-27 18:31:56 -07:00
mbecker20 a18f33b95e formalize the terminal message variants 2025-10-27 18:31:30 -07:00
mbecker20 f35b00ea95 bump clap dependency 2025-10-27 16:18:30 -07:00
mbecker20 70fab08520 clean up terminal modules 2025-10-27 16:17:20 -07:00
mbecker20 0331780a5f rename variables shell -> command 2025-10-27 11:08:57 -07:00
mbecker20 06cdfd2bbc Terminal -> Terminals tabs 2025-10-27 02:53:06 -07:00
mbecker20 1555202569 Create Terminal don't auto set request after changed 2025-10-27 02:42:06 -07:00
mbecker20 5139622aad deploy 2.0.0-dev-82 2025-10-27 02:28:48 -07:00
mbecker20 61ce2ee3db improve new terminal 2025-10-27 02:04:15 -07:00
mbecker20 3171c14f2b comment on spawn process reaper 2025-10-27 01:41:06 -07:00
mbecker20 521db748d8 deploy 2.0.0-dev-81 2025-10-27 01:27:42 -07:00
mbecker20 35bf224080 deploy 2.0.0-dev-80 2025-10-27 01:21:44 -07:00
mbecker20 e0b31cfe51 CreateTerminal only shows resources which are actually available to connect to 2025-10-27 00:44:56 -07:00
mbecker20 0a890078b0 deploy 2.0.0-dev-79 2025-10-27 00:38:08 -07:00
mbecker20 df97ced7a4 deploy 2.0.0-dev-78 2025-10-27 00:03:26 -07:00
mbecker20 d4e5e2e6d8 add execute_<>_terminal convenience methods 2025-10-26 23:35:17 -07:00
mbecker20 19aa60dcb5 deploy 2.0.0-dev-77 2025-10-26 23:21:15 -07:00
mbecker20 fc19c53e6f deploy 2.0.0-dev-76 2025-10-26 23:00:59 -07:00
mbecker20 4f0af960db Big Terminal refactor + most commands run directly / bypass 'sh -c "..."' 2025-10-26 23:00:35 -07:00
mbecker20 e2ec5258fb add "New" kb shortcut 2025-10-23 23:55:24 -07:00
mbecker20 49b6545a02 reorder cli command list 2025-10-23 23:53:10 -07:00
mbecker20 0aabaa9e62 deploy 2.0.0-dev-75 2025-10-23 12:23:10 -07:00
mbecker20 dc65986eab binaries still built with bullseye for compat, but final images use trixie 2025-10-23 12:22:50 -07:00
mbecker20 1d8f28437d km attach <CONTAINER> 2025-10-23 12:22:02 -07:00
mbecker20 c1502e89c2 deploy 2.0.0-dev-74 2025-10-23 11:51:40 -07:00
mbecker20 0bd15fc442 ResourceQuery.names supports names or ids 2025-10-23 11:23:37 -07:00
mbecker20 5a3621b02e km exec 2025-10-23 01:55:50 -07:00
mbecker20 38192e2dac deploy 2.0.0-dev-73 2025-10-23 00:56:15 -07:00
mbecker20 5d271d5547 use Ping timeout to handle reconnect if for some reason network cuts but ws doesn't receive Close 2025-10-23 00:55:51 -07:00
mbecker20 11fb67a35b ssh use cancel token so stdout.write_all isn't cancelled mid-write, which leads to undefined behavior 2025-10-23 00:14:17 -07:00
mbecker20 a80499dcc4 improve stack config files responsive 2025-10-22 19:02:30 -07:00
mbecker20 8c76b8487f alert responsive, better Server terminal disabled 2025-10-22 13:48:08 -07:00
mbecker20 2b32d9042a deploy 2.0.0-dev-72 2025-10-22 01:00:19 -07:00
mbecker20 dc48f1f2ca deploy 2.0.0-dev-71 2025-10-22 00:50:02 -07:00
mbecker20 8e7b7bdcf1 deploy 2.0.0-dev-70 2025-10-22 00:44:54 -07:00
mbecker20 f11d64f72e add 'init' param to make 'execute_terminal' in single call possible 2025-10-22 00:44:33 -07:00
mbecker20 2ffae85180 dashboard table section headers link to resources page 2025-10-22 00:03:12 -07:00
mbecker20 bd79d0f1e0 km ssh <SERVER> [COMMAND] -n [NAME] 2025-10-21 23:55:36 -07:00
mbecker20 e890b1f675 deploy 2.0.0-dev-69 2025-10-21 23:32:18 -07:00
mbecker20 3b7de25c30 Shift + X - Terminals, Shift + N - New (Resource, Terminal) 2025-10-21 16:11:27 -07:00
mbecker20 793bb99f31 nav to terminal on create 2025-10-21 16:00:50 -07:00
mbecker20 d465c9f273 deploy 2.0.0-dev-68 2025-10-21 15:51:38 -07:00
mbecker20 ce641a8974 terminal page 2025-10-21 15:51:18 -07:00
mbecker20 1b89ceb122 deploy 2.0.0-dev-67 2025-10-21 02:50:21 -07:00
mbecker20 2dbc011d26 remove unneeded log on client terminal disconnect 2025-10-21 02:33:19 -07:00
mbecker20 246da88ae1 deploy 2.0.0-dev-66 2025-10-21 02:29:12 -07:00
mbecker20 a8c16f64b1 km ssh 2025-10-21 02:28:42 -07:00
mbecker20 a5b711a348 stack tabs localstorage increment 2025-10-20 20:35:08 -07:00
mbecker20 9666e9ad83 Fix monitoring table with proper server version component 2025-10-20 03:01:07 -07:00
mbecker20 7479640c73 add hover information for mysterious server header icons 2025-10-20 02:53:18 -07:00
mbecker20 4823825035 give websocket indicator info on hover 2025-10-20 02:35:12 -07:00
mbecker20 23897a7acf clippy 2025-10-20 02:16:52 -07:00
mbecker20 20d5588b5c deploy 2.0.0-dev-65 2025-10-20 02:15:15 -07:00
mbecker20 f7e15ccde5 progress on terminals page 2025-10-20 02:14:51 -07:00
mbecker20 cf7623b1fc combine all resources / table view into dashboard 2025-10-20 01:40:27 -07:00
mbecker20 d3c464c05d start Terminals management page 2025-10-20 00:42:45 -07:00
mbecker20 5c9d416aa4 prog on docs update 2025-10-19 23:33:41 -07:00
mbecker20 aabcd88312 update connect-servers docs 2025-10-19 23:07:50 -07:00
mbecker20 9d2624c6bc clarify root directory in periphery config file 2025-10-19 23:07:19 -07:00
mbecker20 ee11fb0b6c clean up setup script 2025-10-19 23:07:02 -07:00
mbecker20 45adfbddd0 mounting custom CA 2025-10-19 23:06:48 -07:00
mbecker20 d26d035dc6 clean up docs intro 2025-10-19 22:03:17 -07:00
mbecker20 e673ba0adf deploy 2.0.0-dev-64 2025-10-19 21:48:15 -07:00
mbecker20 f876facfa7 improve git status message / failure propogation 2025-10-19 21:47:29 -07:00
mbecker20 3a47d57478 container class px-[1.2rem] 2025-10-19 20:31:40 -07:00
mbecker20 a707028277 responsive tweaks 2025-10-19 20:07:30 -07:00
mbecker20 0c6276c677 fix Resources / Containers mobile 2025-10-19 19:51:28 -07:00
mbecker20 fc9c6706f1 keep more descriptive settings header mobile 2025-10-19 13:24:44 -07:00
mbecker20 7674269ce9 fix user dropdown not showing username mobile 2025-10-19 13:11:34 -07:00
mbecker20 3b511c5adc improve server terminal mobile responsiveness 2025-10-19 13:00:30 -07:00
mbecker20 87221a10e9 fix mobile ContainerTerminal responsiveness 2025-10-19 12:56:11 -07:00
mbecker20 450cb6a148 fix stack config files mobile responsiveness 2025-10-19 12:46:51 -07:00
mbecker20 f252cefb21 responsive server docker tab 2025-10-19 12:37:26 -07:00
mbecker20 7855e9d688 run dkf 2025-10-19 12:30:59 -07:00
mbecker20 feb263c15f more type safe tabs 2025-10-19 12:27:55 -07:00
mbecker20 4f8d1c22cc rest of tabs also use mobile friendly 2025-10-19 12:11:11 -07:00
mbecker20 60bd47834e deploy 2.0.0-dev-63 2025-10-19 11:48:09 -07:00
mbecker20 4d632a6b61 improve resources mobile tabs responsiveness 2025-10-19 11:47:47 -07:00
mbecker20 381dd76723 deploy 2.0.0-dev-62 2025-10-19 01:37:10 -07:00
mbecker20 077e28a5fe fix ConfigList too wide on mobile 2025-10-19 01:36:50 -07:00
mbecker20 6b02aaed7d hide core pubkey copy if origin not https 2025-10-19 01:28:45 -07:00
mbecker20 e466944c05 improve mobile settings view 2025-10-19 01:24:41 -07:00
mbecker20 8ff94b7465 deploy 2.0.0-dev-61 2025-10-19 00:35:26 -07:00
mbecker20 b17df5ed7b show host public ip 2025-10-19 00:34:52 -07:00
mbecker20 207dc30206 cli is distroless, no shell / update-ca-certificates 2025-10-18 22:12:44 -07:00
mbecker20 c3eb386bdb fix copy entrypoint 2025-10-18 22:07:16 -07:00
mbecker20 4279e46892 deploy 2.0.0-dev-60 2025-10-18 12:59:19 -07:00
mbecker20 8d3d2fee12 use entrypoint scripts to make update-ca-certificates consistent when using custom CMD 2025-10-18 12:58:55 -07:00
mbecker20 1df36c4266 deploy 2.0.0-dev-59 2025-10-18 11:36:07 -07:00
mbecker20 36f7ad33c7 core and periphery images auto run update-ca-certificates on start, only need to mount in. 2025-10-18 11:35:45 -07:00
mbecker20 ec34b2c139 deploy 2.0.0-dev-58 2025-10-18 11:02:11 -07:00
mbecker20 d14c28d1f2 new otel instrumentation 2025-10-18 11:01:47 -07:00
mbecker20 68f7a0e9ce all info menu to top of settings 2025-10-18 00:45:59 -07:00
mbecker20 50f0376f0a Add Core title and public key to top of Settings 2025-10-18 00:01:41 -07:00
mbecker20 bbd53747ad fix km ps -h description 2025-10-17 17:17:18 -07:00
mbecker20 6a2adf1f83 tweak logs 2025-10-16 01:06:37 -07:00
mbecker20 128b15b94f deploy 2.0.0-dev-57 2025-10-16 00:59:46 -07:00
mbecker20 8d74b377b7 more otel refinements 2025-10-16 00:59:20 -07:00
mbecker20 d7e972e5c6 stack ui doesn't show project missing when deploying 2025-10-15 23:49:26 -07:00
mbecker20 e5cb4aac5a Fix: Webhook triggered checks linked repo branch for build, stack, sync 2025-10-15 18:06:43 -07:00
mbecker20 d0f62f8326 rework tracing events / improve opentelemetry output 2025-10-15 01:41:18 -07:00
mbecker20 47c4091a4b onboarding key uses recognizable key 2025-10-14 16:57:35 -07:00
mbecker20 973480e2b3 remove all the unnecessary instrument debug 2025-10-14 00:33:53 -07:00
mbecker20 b9e1cc87d2 remove instrument from validate_cancel_repo_build 2025-10-13 23:52:55 -07:00
mbecker20 05d20c8603 deploy 2.0.0-dev-56 2025-10-13 22:05:07 -07:00
mbecker20 fe2d68a001 fix config loading 2025-10-13 22:04:42 -07:00
mbecker20 26fd5b2a6d deploy 2.0.0-dev-55 2025-10-13 20:30:40 -07:00
mbecker20 76457bcb61 apply env / shell interpolation as *final* config loading stage, to include env vars. 2025-10-13 20:26:13 -07:00
mbecker20 ebd2c2238d bump deps 2025-10-13 19:51:05 -07:00
mbecker20 b7fc1bef7b refine default env 2025-10-13 13:53:12 -07:00
mbecker20 50b9f2e1bf deploy 2.0.0-dev-54 2025-10-13 13:06:23 -07:00
mbecker20 41ce86f6ab deploy 2.0.0-dev-53 2025-10-12 20:00:47 -07:00
mbecker20 7a21c01e52 tweak 2025-10-12 19:59:09 -07:00
mbecker20 e63e282510 small clean up 2025-10-12 19:56:15 -07:00
mbecker20 5456b36c18 deploy 2.0.0-dev-52 2025-10-12 13:55:39 -07:00
mbecker20 fcfb58a7e9 periphery with server disabled can initialize core public key file 2025-10-12 13:55:15 -07:00
mbecker20 2203004a74 move periphery in memory state to state.rs 2025-10-12 13:15:52 -07:00
mbecker20 996fb49823 periphery server_enabled /version route 2025-10-12 12:56:29 -07:00
mbecker20 35d22c77a2 Core add non authed /version route 2025-10-12 12:55:14 -07:00
mbecker20 44ab89600f Simpllify Option + Result into one encoding layer 2025-10-12 03:24:00 -07:00
mbecker20 0900e48cb8 ntfy / pushover url interpolation 2025-10-12 01:34:07 -07:00
mbecker20 c530a46a27 deploy 2.0.0-dev-51 2025-10-12 01:09:35 -07:00
mbecker20 f69c8db3ea pass through whether Periphery docker daemon connection is ok 2025-10-12 01:08:45 -07:00
mbecker20 48f2f651e1 periphery runs with logs if couldn't connect to docker daemon 2025-10-12 01:05:20 -07:00
mbecker20 bdb5b4185e rename some websocket fields 2025-10-12 00:28:55 -07:00
mbecker20 42a7b8c19b move connection queries to periphery_client 2025-10-12 00:08:59 -07:00
mbecker20 ded17e4840 more encoding refine 2025-10-12 00:05:16 -07:00
mbecker20 80fb1e6889 more on encoding 2025-10-11 14:11:07 -07:00
mbecker20 1dc861f538 fix periphery keys init when config.private_key is not explicitly defined. 2025-10-11 12:00:29 -07:00
mbecker20 3da63395fd fix EncodedOption docs 2025-10-10 00:32:05 -07:00
mbecker20 c40cbc4d77 deploy 2.0.0-dev-50 2025-10-09 23:40:42 -07:00
mbecker20 05e352e88c attach working 2025-10-09 23:28:27 -07:00
mbecker20 5884c09fb8 fix fe 2025-10-09 22:05:36 -07:00
mbecker20 f8add38043 backend for container attach 2025-10-09 21:53:26 -07:00
mbecker20 501f734e8b deploy 2.0.0-dev-49 2025-10-09 20:13:17 -07:00
mbecker20 de62732ac8 bump jwt lib 2025-10-09 20:12:51 -07:00
mbecker20 bfa61058cd remove github only managed repo webhooks feature. Not well implemented or documented, also provider specific. 2025-10-09 20:06:06 -07:00
mbecker20 72ca6d9910 deploy 2.0.0-dev-48 2025-10-09 19:36:47 -07:00
mbecker20 4d1ac32ad3 clippy 2025-10-09 18:24:50 -07:00
mbecker20 927e5959fa move encoded message schemas between core / periphery into periphery_client 2025-10-09 18:23:44 -07:00
mbecker20 37ccc6e1ef isolate encoding out of transport 2025-10-09 18:11:49 -07:00
mbecker20 deaa8754f3 slowly better ergonomics 2025-10-09 17:29:05 -07:00
mbecker20 dd8ac67c72 clippy and fmt 2025-10-09 16:26:06 -07:00
mbecker20 be4457c9cf deploy 2.0.0-dev-47 2025-10-09 15:41:06 -07:00
mbecker20 1868421815 strictly typed transport bytes encoding 2025-10-09 15:40:42 -07:00
mbecker20 366f7a12b4 Enumerated transport message types 2025-10-08 20:36:16 -07:00
mbecker20 75119370df standardize key rotation with wrapper 2025-10-08 17:45:45 -07:00
mbecker20 9e85b9d4c8 deploy 2.0.0-dev-46 2025-10-08 03:59:22 -07:00
mbecker20 8afbbf23dc deploy 2.0.0-dev-45 2025-10-08 02:48:24 -07:00
mbecker20 770a1116a1 fix: RotateCoreKeys also needs to store the new keys in mem 2025-10-08 02:48:01 -07:00
mbecker20 0b4aebbc24 periphery refresh panics if server_enabled, and core public key fails to parse. 2025-10-08 02:32:52 -07:00
mbecker20 f1696e26e4 deploy 2.0.0-dev-44 2025-10-08 02:11:38 -07:00
mbecker20 1a7b682301 RotateCoreKeys api 2025-10-08 02:11:17 -07:00
mbecker20 b0110b05aa deploy 2.0.0-dev-43 2025-10-08 00:02:34 -07:00
mbecker20 561b490f26 write files potentially containing secrets as 0600 2025-10-07 23:59:53 -07:00
mbecker20 cac1f0b42e align server standard and monitoring tables 2025-10-07 19:41:57 -07:00
mbecker20 28886fb304 fix typos 2025-10-07 14:32:00 -07:00
mbecker20 fb84d4cf7d deploy 2.0.0-dev-42 2025-10-07 01:59:04 -07:00
mbecker20 31e9624556 auto_rotate_keys config 2025-10-07 01:58:13 -07:00
mbecker20 3864bb7115 onboarding key expiry view 2025-10-07 01:16:28 -07:00
mbecker20 cea8601246 remove deleted server from onboarding key copy server 2025-10-07 00:44:36 -07:00
mbecker20 a546364bf3 deploy 2.0.0-dev-41 2025-10-07 00:29:33 -07:00
mbecker20 c8c62ea562 core public keys improve refresh 2025-10-07 00:27:47 -07:00
mbecker20 845e8780c7 improve server stats UI 2025-10-06 23:45:26 -07:00
mbecker20 db60347566 deploy 2.0.0-dev-40 2025-10-06 22:12:40 -07:00
mbecker20 c3ea0239d6 fix passkey support 2025-10-06 22:12:15 -07:00
mbecker20 e9d13449bf improve ws trait ergonomics 2025-10-06 20:02:06 -07:00
mbecker20 2daa92a639 working with safer transport message api 2025-10-06 19:16:03 -07:00
mbecker20 6473080078 deploy 2.0.0-dev-39 2025-10-06 03:14:15 -07:00
mbecker20 d3957f65dc schedule alert send before not after 2025-10-06 03:13:48 -07:00
mbecker20 cb34969f1e move skip label to be built into images 2025-10-06 03:07:11 -07:00
mbecker20 55a0a8cd05 deploy 2.0.0-dev-38 2025-10-06 02:38:42 -07:00
mbecker20 89f08372c6 CloseAlert 2025-10-06 02:38:16 -07:00
mbecker20 6a3ce2d426 config log some errors 2025-10-06 02:13:36 -07:00
mbecker20 4928378d46 fmt 2025-10-06 02:03:03 -07:00
mbecker20 eea222cfba simplify periphery config by removing option 2025-10-06 02:02:31 -07:00
mbecker20 6e9cc2dc77 deploy 2.0.0-dev-37 2025-10-06 01:40:24 -07:00
mbecker20 55d45084d0 comment 2025-10-06 01:40:00 -07:00
mbecker20 9657a44049 Improve config toml / yaml / json interpolation support 2025-10-06 01:38:48 -07:00
mbecker20 51fa9ae3c2 update setup script readme 2025-10-06 00:29:35 -07:00
mbecker20 5fd256444e improve the setup script 2025-10-05 23:53:21 -07:00
mbecker20 059716f178 deploy 2.0.0-dev-36 2025-10-05 18:23:43 -07:00
mbecker20 0bee1fe2c5 fix: connect and connect insecure are swapped 2025-10-05 18:20:29 -07:00
mbecker20 1e58c1a958 deploy 2.0.0-dev-35 2025-10-05 17:45:16 -07:00
mbecker20 ed1431db0a improve v1 downgrade 2025-10-05 17:43:46 -07:00
mbecker20 dc769ff159 document periphery_public_key 2025-10-05 17:39:11 -07:00
mbecker20 098f23ac4c configure Core -> Periphery insecure_tls 2025-10-05 17:36:07 -07:00
mbecker20 03f577d22f forgiving periphery public key parsing 2025-10-05 17:02:08 -07:00
mbecker20 95ca217362 deploy 2.0.0-dev-34 2025-10-05 16:52:33 -07:00
mbecker20 6d61045764 support KOMODO_PERIPHERY_PUBLIC_KEY 2025-10-05 16:52:00 -07:00
mbecker20 34e075eaf3 periphery support core_tls_insecure_skip_verify 2025-10-05 16:12:04 -07:00
mbecker20 232dc0bb4e deploy 2.0.0-dev-33 2025-10-05 14:59:23 -07:00
mbecker20 0cc0ee2aab load periphery_public_key 2025-10-05 14:58:52 -07:00
mbecker20 edebe925ff add km maintenance tasks aliases 2025-10-05 14:50:20 -07:00
mbecker20 5fd45bbc7b deploy 2.0.0-dev-32 2025-10-05 14:39:09 -07:00
mbecker20 0a490dadb2 rotation maintenance execution doesn't make individual updates 2025-10-05 14:38:45 -07:00
mbecker20 23847c15bc deploy 2.0.0-dev-31 2025-10-05 14:21:18 -07:00
mbecker20 0d238aee4f onboarding create_builder 2025-10-05 14:20:58 -07:00
mbecker20 98ad6cf5fa create update use uppercase 2025-10-05 13:41:42 -07:00
mbecker20 e35b81630b deploy 2.0.0-dev-30 2025-10-05 13:30:47 -07:00
mbecker20 1215852fe4 onboarding set Server tags 2025-10-05 13:24:38 -07:00
mbecker20 4164b76ff5 onboarded server needs to be enabled 2025-10-05 12:36:04 -07:00
mbecker20 26a9daffeb deploy 2.0.0-dev-29 2025-10-05 05:49:29 -07:00
mbecker20 8bb9f16e9b onboarding save copy server selection 2025-10-05 05:47:51 -07:00
mbecker20 b6eaf76497 Include templates in onboarding selector 2025-10-05 05:44:58 -07:00
mbecker20 073893da0e deploy 2.0.0-dev-28 2025-10-05 05:18:48 -07:00
mbecker20 e71547f1c2 configure server onboarding key 2025-10-05 05:17:56 -07:00
mbecker20 1991627990 move periphery public key to Server info (keep it out of resource sync) 2025-10-05 04:18:59 -07:00
mbecker20 3434d827a3 deploy 2.0.0-dev-27 2025-10-05 02:57:58 -07:00
mbecker20 1ef8b9878a rotate all server keys task 2025-10-05 02:57:27 -07:00
mbecker20 07ddaa8377 tweak 2025-10-05 01:41:44 -07:00
mbecker20 142c08cde4 deploy 2.0.0-dev-26 2025-10-05 01:19:21 -07:00
mbecker20 1aa1422faa periphery private key rotation 2025-10-05 01:18:56 -07:00
mbecker20 1394e8a6b1 Rotate Server private keys 2025-10-05 00:54:56 -07:00
mbecker20 420ee10211 tweaks 2025-10-04 23:59:14 -07:00
mbecker20 e918461dc5 refine onboarding key 2025-10-04 23:36:37 -07:00
mbecker20 4dc9ca27be refactor Periphery onboarding 2025-10-04 16:43:02 -07:00
mbecker20 f49b186f2f consolidate periphery docker apis into single mod 2025-10-04 16:17:32 -07:00
mbecker20 6e039b41f1 deploy 2.0.0-dev-25 2025-10-03 17:51:46 -07:00
mbecker20 e7cd77b022 tweaks 2025-10-03 17:06:14 -07:00
mbecker20 556cbd04c7 server onboarding flow using onboarding key 2025-10-03 17:01:58 -07:00
mbecker20 4e3d181466 default documented setup now uses Periphery -> Core setup 2025-10-03 12:55:06 -07:00
mbecker20 5d4326f46f NOT_FOUND if server not found 2025-10-03 03:17:37 -07:00
mbecker20 4bb486ad0a deploy 2.0.0-dev-24 2025-10-03 02:30:20 -07:00
mbecker20 d29c5112d8 Confirm server public key flow 2025-10-03 02:29:53 -07:00
mbecker20 d41315b8a4 don't navigate to /login for network errors 2025-10-03 01:58:23 -07:00
mbecker20 847404388c deploy 2.0.0-dev-23 2025-10-03 00:48:11 -07:00
mbecker20 eef8ec59b8 deploy 2.0.0-dev-22 2025-10-03 00:19:43 -07:00
mbecker20 9eb32f9ff5 store attempted public keys 2025-10-03 00:13:55 -07:00
mbecker20 859bfe67ef Improve Core side connection handling and fix Periphery -> Core error report 2025-10-02 23:03:58 -07:00
mbecker20 21ea469cd4 add login message 2 sec timeout 2025-10-02 16:00:45 -07:00
mbecker20 7fb902b892 deploy 2.0.0-dev-21 2025-10-02 03:12:59 -07:00
mbecker20 c9c4ac47ee fix clippy 2025-10-02 02:34:23 -07:00
mbecker20 f228cd31f3 deploy 2.0.0-dev-20 2025-10-02 02:33:33 -07:00
mbecker20 4feecb4b97 write key pem files by default when not otherwise provided. 2025-10-02 02:32:13 -07:00
mbecker20 e2680d0942 fix deploy 2025-10-01 21:35:27 -07:00
mbecker20 7422c0730d deploy 2.0.0-dev-19 2025-10-01 21:27:59 -07:00
mbecker20 37ac0dc7e3 update deploy 2025-10-01 21:17:43 -07:00
mbecker20 dccaca1df4 make sure not a config file before include as compose file 2025-10-01 20:32:52 -07:00
mbecker20 886aea4c36 deploy 2.0.0-dev-18 2025-10-01 19:48:40 -07:00
mbecker20 cbca070bae load keys from files 2025-10-01 19:41:32 -07:00
mbecker20 b4bdd401f6 fix unneeded base64 prefix 2025-10-01 02:36:26 -07:00
mbecker20 e546166240 use pkcs8 and spki for private / public key encoding, matching openssl 2025-10-01 02:25:41 -07:00
mbecker20 21689ce0ad periphery support same key gen functions 2025-09-29 23:32:47 -07:00
mbecker20 941787db64 slack client 0.5.0 2025-09-29 12:38:39 -07:00
mbecker20 d4b1aacac3 comment out 2025-09-29 02:21:07 -07:00
mbecker20 30f89461bf deploy 2.0.0-dev-17 2025-09-29 00:57:19 -07:00
mbecker20 a42d1397e9 back to bullseye (for max GLIBC compatibility) 2025-09-29 00:56:19 -07:00
mbecker20 b29313c28f deploy 2.0.0-dev-16 2025-09-29 00:47:17 -07:00
mbecker20 08a246a90c bullseye -> trixie 2025-09-29 00:46:51 -07:00
mbecker20 1a08df28d0 docs and config clean up 2025-09-29 00:06:35 -07:00
mbecker20 a226ffc256 fix json config load from interpolated 2025-09-28 23:20:04 -07:00
mbecker20 b385ee5ec3 start on docs update 2025-09-28 22:59:58 -07:00
mbecker20 c78c34357d remove unnecessary connected to core websocket log 2025-09-28 18:14:57 -07:00
mbecker20 4b7c692f00 deploy 2.0.0-dev-15 2025-09-28 18:02:18 -07:00
mbecker20 1ac98a096e bump async timing util to 1.1.0 to support for timelengths 2025-09-28 17:57:12 -07:00
mbecker20 281a2dc1ce first server configuration works with Periphery -> Core 2025-09-28 14:39:11 -07:00
mbecker20 0fe91378a6 tweak key gen output 2025-09-28 14:12:41 -07:00
mbecker20 11e76d1cf2 deploy 2.0.0-dev-14 2025-09-28 13:10:00 -07:00
mbecker20 a3bcd71105 simplify cache refresh with single periphery call 2025-09-28 13:05:45 -07:00
mbecker20 3ecc56dd76 clean up crypto provider install 2025-09-27 21:40:20 -07:00
mbecker20 7239cbb19b remove extra install crypto provider 2025-09-27 19:37:50 -07:00
mbecker20 a0540f7011 deploy 2.0.0-dev-13 2025-09-27 16:54:00 -07:00
mbecker20 37aea7605e gen types 2025-09-27 14:33:14 -07:00
mbecker20 78be913541 fix stuff after main rebase 2025-09-27 14:26:58 -07:00
mbecker20 c34f5ebf49 update config and compose envs 2025-09-27 14:23:49 -07:00
mbecker20 e5822cefb8 clean up socket handling 2025-09-27 14:23:49 -07:00
mbecker20 4baab194cf centralize the terminal stuff 2025-09-27 14:23:49 -07:00
mbecker20 a896583da6 deploy 2.0.0-dev-12 2025-09-27 14:23:49 -07:00
mbecker20 7b2674c38b deploy 2.0.0-dev-11 2025-09-27 14:23:42 -07:00
mbecker20 d1e32989e3 allow any number of simultaneous inbound / outbound connections (to different Cores) 2025-09-27 14:23:36 -07:00
mbecker20 e802bb3882 periphery support multiple core_public_keys 2025-09-27 14:23:36 -07:00
mbecker20 27a38b1bf5 periphery support multiple simultaneous core connections 2025-09-27 14:23:36 -07:00
mbecker20 2bc8a754be clean up passkey login 2025-09-27 14:23:36 -07:00
mbecker20 7a2a54bec1 dev-10 2025-09-27 14:23:36 -07:00
mbecker20 6a15150d59 don't cleanup server type builders 2025-09-27 14:23:31 -07:00
mbecker20 1b1dca76da deploy 2.0.0-dev-9 2025-09-27 14:23:31 -07:00
mbecker20 a032f0f4ff move system info to server cache 2025-09-27 14:23:25 -07:00
mbecker20 2749d49435 Core -> Periphery connection prefers noise handshake if 'core_public_key' is set 2025-09-27 14:23:25 -07:00
mbecker20 d88e42ef2d add specific server passkey support back 2025-09-27 14:23:25 -07:00
mbecker20 a370e7d121 support passkey auth for Core -> Periphery connection to remove the breaking change 2025-09-27 14:23:25 -07:00
mbecker20 d139ad2b3d always fallback to core config 'periphery_public_key' 2025-09-27 14:23:25 -07:00
mbecker20 8d2d180398 deploy 2.0.0-dev-8 2025-09-27 14:22:48 -07:00
mbecker20 37ca4ca986 fix server update hang 2025-09-27 14:22:42 -07:00
mbecker20 33e73b8543 use warn log 2025-09-27 14:22:42 -07:00
mbecker20 cf6e36e90c periphery server avoid auth fail log spam 2025-09-27 14:22:42 -07:00
mbecker20 9eb8b32f4a create and delete connections on demand 2025-09-27 14:22:42 -07:00
mbecker20 b400add6f1 deploy 2.0.0-dev-7 2025-09-27 14:22:41 -07:00
mbecker20 24adb89d25 execute container exec waits a bit for terminal to init before sending command 2025-09-27 14:22:36 -07:00
mbecker20 4674b2badb deploy 2.0.0-dev-6 2025-09-27 14:22:36 -07:00
mbecker20 65d1a69cb9 Mount ExecuteContainerExec periphery api 2025-09-27 14:22:27 -07:00
mbecker20 0da5718991 store connection channels under the connection 2025-09-27 14:22:27 -07:00
mbecker20 6b26cd120c simplify most of periphery client into bin/core 2025-09-27 14:22:27 -07:00
mbecker20 28e1bb19a4 deploy 2.0.0-dev-5 2025-09-27 14:22:27 -07:00
mbecker20 166107ac07 bail_if_not_connected 2025-09-27 14:22:21 -07:00
mbecker20 d77201880f dashboard Active include GlobalAutoUpdate 2025-09-27 14:22:21 -07:00
mbecker20 1d7629e9b2 Update server address description and placeholders 2025-09-27 14:22:21 -07:00
mbecker20 198f690ca5 Got invalid public key: {public_key} 2025-09-27 14:22:21 -07:00
mbecker20 531c79a144 deploy 2.0.0-dev-4 2025-09-27 14:22:21 -07:00
mbecker20 d685862713 improve Core - Periphery auth error messages 2025-09-27 14:22:09 -07:00
mbecker20 af0f245b5b deploy 2.0.0-dev-3 2025-09-27 14:22:09 -07:00
mbecker20 cba36861b7 deploy 2.0.0-dev-2 2025-09-27 14:22:02 -07:00
mbecker20 2c2c1d47b4 dev-2 2025-09-27 14:22:02 -07:00
mbecker20 3a6b997241 Json and JsonPretty formatting 2025-09-27 14:21:54 -07:00
mbecker20 7122f79b9d add -f json option to key utils (for use with jquery etc. 2025-09-27 14:21:54 -07:00
mbecker20 9bcee8122b tweak 2025-09-27 14:21:54 -07:00
mbecker20 a49c98946e add copy pubkeys 2025-09-27 14:21:54 -07:00
mbecker20 7d222a7241 dev-1 2025-09-27 14:21:54 -07:00
mbecker20 33501dac3e fix Core -> Periphery reconnection 2025-09-27 14:21:44 -07:00
mbecker20 4675dfa736 improve the logging 2025-09-27 14:21:44 -07:00
mbecker20 0be51dc784 move core connection handlers into core binary 2025-09-27 14:21:44 -07:00
mbecker20 52453d1320 set default allowed periphery public key 2025-09-27 14:21:44 -07:00
mbecker20 25da97ac1a basic configure auth 2025-09-27 14:21:44 -07:00
mbecker20 02db5a11d3 pipe through core side private / public key handling 2025-09-27 14:21:44 -07:00
mbecker20 89a5272246 rename passkey -> private_key 2025-09-27 14:21:44 -07:00
mbecker20 ae51ea1ad6 Copy core public key 2025-09-27 14:21:44 -07:00
mbecker20 3bdb4bea16 Core includes public key in CoreInfo 2025-09-27 14:21:44 -07:00
mbecker20 677bb14b5d auth forward error 2025-09-27 14:21:44 -07:00
mbecker20 6700700a80 clean up websocket handlers with many params 2025-09-27 14:21:44 -07:00
mbecker20 996d4aa129 standardize server size header identifier extraction 2025-09-27 14:21:44 -07:00
mbecker20 75894a7282 wire through private keys 2025-09-27 14:21:44 -07:00
mbecker20 2a065edcf1 avoid looping periphery client error 2025-09-27 14:21:44 -07:00
mbecker20 6f3703acfb periphery client makes more sense 2025-09-27 14:21:44 -07:00
mbecker20 59e989ecdf noise library and cli key utilities 2025-09-27 14:21:44 -07:00
mbecker20 951ff34a9e abstract websocket handling implementations on both sides 2025-09-27 14:21:12 -07:00
mbecker20 2d83105500 clean up 2025-09-27 14:21:12 -07:00
mbecker20 3d455f5142 implement noise auth basic 2025-09-27 14:21:12 -07:00
mbecker20 01de8c4a9b use standardized websocket wrappers / traits 2025-09-27 14:21:12 -07:00
mbecker20 d5de338561 outbound connection mode working 2025-09-27 14:21:12 -07:00
mbecker20 58c1afb8ef add login draft for transport 2025-09-27 14:21:12 -07:00
mbecker20 230f357b5a everything over ws working 2025-09-27 14:21:12 -07:00
mbecker20 991c95fff0 execute basically working, still need to clear the response channel upon completion 2025-09-27 14:21:12 -07:00
mbecker20 f6243fe6b1 more cleanup 2025-09-27 14:21:12 -07:00
mbecker20 9feeccba6e container terminal over connection 2025-09-27 14:21:12 -07:00
mbecker20 673c7f3a6b multiplex requests + terminal over single WS 2025-09-27 14:21:12 -07:00
mbecker20 39f900d651 standardize and consolidate logic in transport lib 2025-09-27 14:21:12 -07:00
mbecker20 8a06a0d6ce is work 2025-09-27 14:21:12 -07:00
mbecker20 7789ee4f4a prog 2025-09-27 14:21:12 -07:00
mbecker20 0472b6a7f7 fix after 1.19.4 2025-09-27 14:21:12 -07:00
mbecker20 d1d2227d36 prog 2025-09-27 14:21:11 -07:00
mbecker20 cea7c5fc5e prog on ws connect 2025-09-27 14:21:11 -07:00
705 changed files with 24272 additions and 37410 deletions
+1 -4
View File
@@ -1,5 +1,2 @@
[alias]
xtask = "run --package xtask --"
[build]
rustflags = ["-Wunused-crate-dependencies"]
rustflags = ["-Wunused-crate-dependencies"]
+3 -9
View File
@@ -1,6 +1,6 @@
services:
dev:
image: mcr.microsoft.com/devcontainers/rust:1-bookworm
image: mcr.microsoft.com/devcontainers/rust:1-1-bullseye
volumes:
# Mount the root folder that contains .git
- ../:/workspace:cached
@@ -10,19 +10,13 @@ services:
- stacks:/etc/komodo/stacks
command: sleep infinity
ports:
- "9120:9120"
- "9121:9121"
environment:
KOMODO_HOST: http://localhost:9120
KOMODO_FIRST_SERVER: http://localhost:8120
KOMODO_DATABASE_ADDRESS: db
KOMODO_ENABLE_NEW_USERS: true
KOMODO_LOCAL_AUTH: true
KOMODO_JWT_SECRET: a_random_secret
VITE_KOMODO_HOST: http://localhost:9120
KOMODO_CORS_ALLOWED_ORIGINS: http://localhost:5173
KOMODO_CORS_ALLOW_CREDENTIALS: true
PERIPHERY_SSL_ENABLED: false
KOMODO_SESSION_ALLOW_CROSS_SITE: true
links:
- db
# ...
@@ -36,4 +30,4 @@ volumes:
data:
repo-cache:
repos:
stacks:
stacks:
+3 -9
View File
@@ -10,7 +10,7 @@
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "22.12.0"
"version": "20.12.2"
},
"ghcr.io/devcontainers-community/features/deno:1": {
@@ -28,8 +28,7 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
5173,
9120
9121
],
// Use 'postCreateCommand' to run commands after the container is created.
@@ -37,16 +36,11 @@
"runServices": [
"db"
],
]
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
"remoteEnv": {
// Avoid out of memory error when running `yarn build`
"NODE_OPTIONS": "--max-old-space-size=4096"
}
}
+1 -7
View File
@@ -1,9 +1,3 @@
#!/bin/sh
cargo install typeshare-cli
sudo mkdir -p /etc/komodo/keys
sudo chown -R $(whoami) /etc/komodo
sudo mkdir -p /config/keys
sudo chown -R $(whoami) /config
cargo install typeshare-cli
-56
View File
@@ -1,56 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
# - name: Cache cargo registry
# uses: actions/cache@v4
# with:
# path: |
# ~/.cargo/registry
# ~/.cargo/git
# target
# key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# restore-keys: |
# ${{ runner.os }}-cargo-
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
fmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
Generated
+949 -1903
View File
File diff suppressed because it is too large Load Diff
+54 -58
View File
@@ -3,18 +3,21 @@ resolver = "2"
members = [
"bin/*",
"lib/*",
"xtask",
"client/core/rs",
"client/periphery/rs",
]
[workspace.package]
version = "2.3.1"
version = "2.0.0-dev-120"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
repository = "https://github.com/moghtech/komodo"
homepage = "https://komo.do"
[profile.release]
strip = "debuginfo"
[workspace.dependencies]
# LOCAL
komodo_client = { path = "client/core/rs" }
@@ -30,113 +33,106 @@ git = { path = "lib/git" }
# MOGH
slack = { version = "2.0.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
mogh_error = { version = "1.0.4", default-features = false }
mogh_error = { version = "1.0.3", default-features = false }
derive_default_builder = "0.1.8"
async_timing_util = "1.1.0"
mogh_auth_client = "1.7.0"
mogh_auth_server = "1.7.0"
mogh_auth_client = "1.2.2"
mogh_auth_server = "1.2.6"
mogh_secret_file = "1.0.1"
mogh_validations = "1.0.1"
mogh_rate_limit = "1.0.1"
partial_derive2 = "0.5.0"
mongo_indexed = "2.1.0"
partial_derive2 = "0.4.5"
mongo_indexed = "2.0.2"
mogh_resolver = "1.0.0"
mogh_config = "1.1.0"
mogh_logger = "1.3.4"
mogh_server = "1.5.0"
mogh_config = "1.0.2"
mogh_logger = "1.3.1"
mogh_server = "1.4.4"
toml_pretty = "2.0.0"
mogh_cache = "1.1.2"
mogh_pki = "1.1.3"
mungos = "3.7.0"
mogh_cache = "1.1.1"
mogh_pki = "1.1.0"
mungos = "3.2.2"
svi = "1.2.0"
# ASYNC
reqwest = { version = "0.13.4", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
tokio = { version = "1.53.1", features = ["full"] }
tokio-util = { version = "0.7.19", features = ["io", "codec"] }
tokio-stream = { version = "0.1.19", features = ["sync"] }
pin-project-lite = "0.2.17"
futures-util = "0.3.33"
arc-swap = "1.9.2"
reqwest = { version = "0.13.2", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
tokio = { version = "1.49.0", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["io", "codec"] }
tokio-stream = { version = "0.1.18", features = ["sync"] }
pin-project-lite = "0.2.16"
futures-util = "0.3.32"
arc-swap = "1.8.2"
# SERVER
tokio-tungstenite = { version = "0.30.0", features = ["rustls-tls-native-roots"] }
axum = { version = "0.8.9", features = ["ws", "json", "macros"] }
axum-extra = { version = "0.12.6", features = ["typed-header"] }
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
axum = { version = "0.8.8", features = ["ws", "json", "macros"] }
axum-extra = { version = "0.12.5", features = ["typed-header"] }
# OPENAPI
utoipa-scalar = { version = "0.3.0", features = ["axum"] }
utoipa = "5.5.0"
utoipa = "5.4.0"
# SER/DE
ipnetwork = { version = "0.21.1", features = ["serde"] }
indexmap = { version = "2.14.0", features = ["serde"] }
serde = { version = "1.0.229", features = ["derive"] }
indexmap = { version = "2.13.0", features = ["serde"] }
serde = { version = "1.0.227", features = ["derive"] }
strum = { version = "0.28.0", features = ["derive"] }
bson = { version = "2.15.0" } # must keep in sync with mongodb version
toml = "1.1.3"
toml = "0.9.11"
serde_yaml_ng = "0.10.0"
serde_json = "1.0.151"
serde_qs = "1.1.2"
serde_json = "1.0.149"
serde_qs = "1.0.0"
url = "2.5.8"
# ERROR
anyhow = "1.0.104"
thiserror = "2.0.19"
# SYSTEM
libc = "0.2.189"
anyhow = "1.0.102"
thiserror = "2.0.18"
# LOGGING
tracing = "0.1.44"
# CONFIG
clap = { version = "4.6.4", features = ["derive"] }
clap = { version = "4.5.60", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
# CRYPTO / AUTH
uuid = { version = "1.24.0", features = ["v4", "fast-rng", "serde"] }
rustls = { version = "0.23.42", features = ["aws-lc-rs"] }
data-encoding = "2.11.0"
uuid = { version = "1.21.0", features = ["v4", "fast-rng", "serde"] }
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
data-encoding = "2.10.0"
urlencoding = "2.1.3"
bcrypt = "0.19.2"
hmac = "0.13.0"
sha1 = "0.11.0"
sha2 = "0.11.0"
rand = "0.10.2"
bcrypt = "0.18.0"
hmac = "0.12.1"
sha1 = "0.10.6"
sha2 = "0.10.9"
rand = "0.10.0"
hex = "0.4.3"
# SYSTEM
hickory-resolver = "0.26.1"
hickory-resolver = "0.25.2"
portable-pty = "0.9.0"
shell-escape = "0.1.5"
crossterm = "0.29.0"
bollard = "0.21.0"
sysinfo = "0.39.6"
shlex = "2.0.1"
bollard = "0.20.1"
sysinfo = "0.38.2"
shlex = "1.3.0"
# CLOUD
aws-config = "1.10.0"
aws-sdk-ec2 = "1.241.0"
aws-credential-types = "1.3.0"
aws-config = "1.8.14"
aws-sdk-ec2 = "1.214.0"
aws-credential-types = "1.2.13"
## CRON
english-to-cron = "0.1.7"
chrono-tz = "0.10.4"
chrono = "0.4.45"
chrono = "0.4.44"
croner = "3.0.1"
# MISC
async-compression = { version = "0.4.42", features = ["tokio", "gzip"] }
schemars = { version = "1.2.1", features = ["indexmap2"] }
async-compression = { version = "0.4.41", features = ["tokio", "gzip"] }
derive_builder = "0.20.2"
comfy-table = "7.2.2"
typeshare = "1.0.5"
wildcard = "0.3.0"
colored = "3.1.1"
bytes = "1.12.1"
regex = "1.13.1"
[profile.release]
strip = "debuginfo"
bytes = "1.11.1"
regex = "1.12.3"
+1 -1
View File
@@ -1,4 +1,4 @@
const cmd = "km run -y action deploy-komodo-ui-change";
const cmd = "km run -y action deploy-komodo-fe-change";
new Deno.Command("bash", {
args: ["-c", cmd],
}).spawn();
+1 -2
View File
@@ -1,7 +1,7 @@
## Builds the Komodo Core, Periphery, and Util binaries
## for a specific architecture. Requires OpenSSL 3 or later.
FROM rust:1.97.1-bookworm AS builder
FROM rust:1.93.1-bookworm AS builder
RUN cargo install cargo-strip
WORKDIR /builder
@@ -12,7 +12,6 @@ COPY ./client/periphery ./client/periphery
COPY ./bin/core ./bin/core
COPY ./bin/periphery ./bin/periphery
COPY ./bin/cli ./bin/cli
COPY ./xtask ./xtask
# Compile bin
RUN \
+1 -1
View File
@@ -3,7 +3,7 @@
## Uses chef for dependency caching to help speed up back-to-back builds.
FROM lukemathwalker/cargo-chef:latest-rust-1.97.1-bookworm AS chef
FROM lukemathwalker/cargo-chef:latest-rust-1.93.1-bookworm AS chef
WORKDIR /builder
# Plan just the RECIPE to see if things have changed
+1 -2
View File
@@ -3,6 +3,7 @@ name = "komodo_cli"
description = "Command line tool for Komodo"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -31,9 +32,7 @@ tracing.workspace = true
colored.workspace = true
dotenvy.workspace = true
anyhow.workspace = true
bcrypt.workspace = true
chrono.workspace = true
rustls.workspace = true
tokio.workspace = true
serde.workspace = true
clap.workspace = true
+1 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.97.1-trixie AS builder
FROM rust:1.93.1-bullseye AS builder
RUN cargo install cargo-strip
WORKDIR /builder
+2 -2
View File
@@ -1,7 +1,7 @@
## Assumes the latest binaries for x86_64 and aarch64 are already built (by binaries.Dockerfile).
## Since theres no heavy build here, QEMU multi-arch builds are fine for this image.
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64
ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
@@ -9,7 +9,7 @@ ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
FROM ${X86_64_BINARIES} AS x86_64
FROM ${AARCH64_BINARIES} AS aarch64
FROM debian:trixie-slim
FROM debian:bullseye-slim
WORKDIR /app
+1 -1
View File
@@ -1,6 +1,6 @@
## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile).
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
# This is required to work with COPY --from
FROM ${BINARIES_IMAGE} AS binaries
+27 -25
View File
@@ -7,16 +7,16 @@ use futures_util::{
FutureExt, TryStreamExt, stream::FuturesUnordered,
};
use komodo_client::{
api::read::{self, ListAllContainers, ListServers},
api::read::{
InspectDockerContainer, ListAllDockerContainers, ListServers,
},
entities::{
config::cli::args::container::{
Container, ContainerCommand, InspectContainer,
},
docker::{
self,
container::{
ContainerListItem, ContainerSortBy, ContainerStateStatusEnum,
},
container::{ContainerListItem, ContainerStateStatusEnum},
},
},
};
@@ -44,12 +44,11 @@ async fn list_containers(
down,
links,
reverse,
containers: terms,
containers: names,
images,
networks,
servers,
format,
page,
command: _,
}: &Container,
) -> anyhow::Result<()> {
@@ -61,16 +60,9 @@ async fn list_containers(
.into_iter()
.map(|s| (s.id.clone(), s))
.collect::<HashMap<_, _>>())),
client.read(ListAllContainers {
client.read(ListAllDockerContainers {
servers: Default::default(),
tags: Default::default(),
terms: terms.clone(),
state: Default::default(),
limit: Some(100),
// Page is more naturally given starting as 1, 2, 3.
page: if *page == 0 { 0 } else { *page - 1 },
sort_by: ContainerSortBy::Server,
sort_desc: false,
containers: Default::default(),
}),
)?;
@@ -86,6 +78,7 @@ async fn list_containers(
(Some(server.name.as_str()), c)
});
let names = parse_wildcards(names);
let servers = parse_wildcards(servers);
let images = parse_wildcards(images);
let networks = parse_wildcards(networks);
@@ -112,6 +105,7 @@ async fn list_containers(
);
state_check
&& network_check
&& matches_wildcards(&names, &[c.name.as_str()])
&& matches_wildcards(
&servers,
&server_name
@@ -151,15 +145,9 @@ pub async fn inspect_container(
.into_iter()
.map(|s| (s.id.clone(), s))
.collect::<HashMap<_, _>>())),
client.read(ListAllContainers {
servers: inspect.servers.clone(),
tags: Default::default(),
terms: vec![inspect.container.clone()],
state: Default::default(),
limit: Some(0),
page: 0,
sort_by: ContainerSortBy::Server,
sort_desc: false,
client.read(ListAllDockerContainers {
servers: Default::default(),
containers: Default::default()
}),
)?;
@@ -174,11 +162,25 @@ pub async fn inspect_container(
c.server_id = Some(server.name.clone());
});
let names = [inspect.container.to_string()];
let names = parse_wildcards(&names);
let servers = parse_wildcards(&inspect.servers);
let mut containers = containers
.into_iter()
.filter(|c| {
matches_wildcards(&names, &[c.name.as_str()])
&& matches_wildcards(
&servers,
&c.server_id
.as_deref()
.map(|i| vec![i])
.unwrap_or_default(),
)
})
.map(|c| async move {
client
.read(read::InspectContainer {
.read(InspectDockerContainer {
container: c.name,
server: c.server_id.context("No server...")?,
})
-13
View File
@@ -1,13 +0,0 @@
use anyhow::Context as _;
use komodo_client::api::read::GetCoreInfo;
pub async fn handle() -> anyhow::Result<()> {
let client = super::komodo_client().await?;
let info = client.read(GetCoreInfo {}).await?;
println!(
"{}",
serde_json::to_string_pretty(&info)
.context("Failed to serialize core info to JSON")?
);
Ok(())
}
-101
View File
@@ -1,101 +0,0 @@
use anyhow::Context as _;
use database::bson::doc;
use komodo_client::{
api::read::FindUser,
entities::{
api_key::ApiKey, config::cli::args::create::CreateApiKey,
komodo_timestamp, random_string,
},
};
use serde_json::json;
use crate::config::cli_config;
pub async fn create(
CreateApiKey {
name,
for_user,
expires,
use_api,
}: &CreateApiKey,
) -> anyhow::Result<()> {
let expires = if let Some(expires_days) = expires {
// now + expires in ms
komodo_timestamp() + expires_days * 24 * 60 * 60 * 1000
} else {
0
};
if *use_api {
// USE API
let client = crate::command::komodo_client().await?;
let keys = if let Some(username) = for_user {
// For service user
let user = client
.read(FindUser {
user: username.to_string(),
})
.await?;
client
.write(
komodo_client::api::write::CreateApiKeyForServiceUser {
user_id: user.id,
name: name.clone().unwrap_or_default(),
expires,
},
)
.await?
} else {
// For self
client
.auth_manage(komodo_client::api::auth::manage::CreateApiKey {
name: name.clone().unwrap_or_default(),
expires: expires as u64,
})
.await?
};
println!(
"{}",
serde_json::to_string_pretty(&keys)
.context("Failed to serialize api keys to JSON")?
);
} else {
// USE DATABASE
let db = database::Client::new(&cli_config().database).await?;
let user = db
.users
.find_one(doc! { "username": for_user })
.await
.context("Failed to query database for user")?
.context("No user found with given username")?;
let key = format!("K_{}_K", random_string(40));
let secret = format!("S_{}_S", random_string(40));
let hashed_secret = bcrypt::hash(&secret, 10)
.context("Failed at hashing secret string")?;
db.api_keys
.insert_one(&ApiKey {
name: name.clone().unwrap_or_default(),
user_id: user.id,
key: key.clone(),
secret: hashed_secret.clone(),
created_at: komodo_timestamp(),
expires,
})
.await?;
println!(
"{}",
serde_json::to_string_pretty(
&json!({ "key": key, "secret": secret })
)
.context("Failed to serialize api keys to JSON")?
);
}
Ok(())
}
-13
View File
@@ -1,13 +0,0 @@
use komodo_client::entities::config::cli::args::create::CreateCommand;
mod api_key;
mod onboarding_key;
pub async fn handle(command: &CreateCommand) -> anyhow::Result<()> {
match command {
CreateCommand::ApiKey(api_key) => api_key::create(api_key).await,
CreateCommand::OnboardingKey(onboarding_key) => {
onboarding_key::create(onboarding_key).await
}
}
}
@@ -1,46 +0,0 @@
use anyhow::Context as _;
use komodo_client::entities::{
config::cli::args::create::CreateOnboardingKey, komodo_timestamp,
};
pub async fn create(
CreateOnboardingKey {
name,
expires,
private_key,
tags,
privileged,
copy_server,
create_builder,
}: &CreateOnboardingKey,
) -> anyhow::Result<()> {
let expires = if let Some(expires_days) = expires {
// now + expires in ms
komodo_timestamp() + expires_days * 24 * 60 * 60 * 1000
} else {
0
};
// USE API
let client = crate::command::komodo_client().await?;
let key = client
.write(komodo_client::api::write::CreateOnboardingKey {
name: name.clone().unwrap_or_default(),
expires,
private_key: private_key.clone(),
tags: tags.clone(),
privileged: *privileged,
copy_server: copy_server.clone().unwrap_or_default(),
create_builder: *create_builder,
})
.await?;
println!(
"{}",
serde_json::to_string_pretty(&key)
.context("Failed to serialize onboarding key to JSON")?
);
Ok(())
}
+550 -133
View File
@@ -28,141 +28,558 @@ pub async fn handle(
}
println!("\n{}: Execution", "Mode".dimmed());
macro_rules! handle_execution {
(
execute: [$($ExecVariant:ident),* $(,)?],
batch: [$($BatchVariant:ident),* $(,)?],
) => {{
// Print data
match execution {
$(Execution::$ExecVariant(data) => println!("{}: {data:?}", "Data".dimmed()),)*
$(Execution::$BatchVariant(data) => println!("{}: {data:?}", "Data".dimmed()),)*
Execution::CommitSync(data) => println!("{}: {data:?}", "Data".dimmed()),
Execution::Sleep(data) => println!("{}: {data:?}", "Data".dimmed()),
Execution::None(data) => println!("{}: {data:?}", "Data".dimmed()),
}
$crate::command::wait_for_enter("run execution", yes)?;
info!("Running Execution...");
let client = $crate::command::komodo_client().await?;
// Execute and get result
match execution.clone() {
$(
Execution::$ExecVariant(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
)*
$(
Execution::$BatchVariant(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
)*
Execution::CommitSync(request) => client
.write(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::Sleep(request) => {
let duration =
Duration::from_millis(request.duration_ms as u64);
tokio::time::sleep(duration).await;
println!("Finished sleeping!");
std::process::exit(0)
}
Execution::None(_) => unreachable!(),
}
}};
match execution {
Execution::None(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RunAction(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchRunAction(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RunProcedure(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchRunProcedure(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RunBuild(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchRunBuild(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CancelBuild(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::Deploy(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchDeploy(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PullDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StartDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RestartDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PauseDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::UnpauseDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StopDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DestroyDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchDestroyDeployment(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CloneRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchCloneRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PullRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchPullRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BuildRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchBuildRepo(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CancelRepoBuild(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StartContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RestartContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PauseContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::UnpauseContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StopContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DestroyContainer(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StartAllContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RestartAllContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PauseAllContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::UnpauseAllContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StopAllContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneContainers(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DeleteNetwork(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneNetworks(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DeleteImage(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneImages(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DeleteVolume(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneVolumes(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneDockerBuilders(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneBuildx(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PruneSystem(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RunSync(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CommitSync(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DeployStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchDeployStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DeployStackIfChanged(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchDeployStackIfChanged(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PullStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchPullStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StartStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RestartStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::PauseStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::UnpauseStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::StopStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::DestroyStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BatchDestroyStack(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RunStackService(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::TestAlerter(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::SendAlert(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RemoveSwarmNodes(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RemoveSwarmStacks(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RemoveSwarmServices(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CreateSwarmConfig(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RotateSwarmConfig(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RemoveSwarmConfigs(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::CreateSwarmSecret(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RotateSwarmSecret(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RemoveSwarmSecrets(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::ClearRepoCache(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::BackupCoreDatabase(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::GlobalAutoUpdate(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RotateAllServerKeys(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::RotateCoreKeys(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
Execution::Sleep(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
}
let res = handle_execution!(
execute: [
RunAction,
CancelAction,
RunProcedure,
CancelProcedure,
RunBuild,
CancelBuild,
Deploy,
PullDeployment,
StartDeployment,
RestartDeployment,
PauseDeployment,
UnpauseDeployment,
StopDeployment,
DestroyDeployment,
CloneRepo,
PullRepo,
BuildRepo,
CancelRepoBuild,
StartContainer,
RestartContainer,
PauseContainer,
UnpauseContainer,
StopContainer,
DestroyContainer,
StartAllContainers,
RestartAllContainers,
PauseAllContainers,
UnpauseAllContainers,
StopAllContainers,
PruneContainers,
DeleteNetwork,
PruneNetworks,
DeleteImage,
PruneImages,
DeleteVolume,
PruneVolumes,
PruneDockerBuilders,
PruneBuildx,
PruneSystem,
RunSync,
DeployStack,
DeployStackIfChanged,
PullStack,
StartStack,
RestartStack,
PauseStack,
UnpauseStack,
StopStack,
DestroyStack,
RunStackService,
TestAlerter,
SendAlert,
RemoveSwarmNodes,
UpdateSwarmNode,
RemoveSwarmStacks,
RemoveSwarmServices,
CreateSwarmConfig,
RotateSwarmConfig,
RemoveSwarmConfigs,
CreateSwarmSecret,
RotateSwarmSecret,
RemoveSwarmSecrets,
ClearRepoCache,
BackupCoreDatabase,
GlobalAutoUpdate,
RotateAllServerKeys,
RotateCoreKeys,
],
batch: [
BatchRunAction,
BatchRunProcedure,
BatchRunBuild,
BatchDeploy,
BatchDestroyDeployment,
BatchCloneRepo,
BatchPullRepo,
BatchBuildRepo,
BatchDeployStack,
BatchDeployStackIfChanged,
BatchPullStack,
BatchDestroyStack,
],
);
super::wait_for_enter("run execution", yes)?;
info!("Running Execution...");
let client = super::komodo_client().await?;
let res = match execution.clone() {
Execution::RunAction(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchRunAction(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::RunProcedure(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchRunProcedure(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::RunBuild(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchRunBuild(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::CancelBuild(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::Deploy(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchDeploy(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::PullDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StartDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RestartDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PauseDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::UnpauseDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StopDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DestroyDeployment(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchDestroyDeployment(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::CloneRepo(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchCloneRepo(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::PullRepo(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchPullRepo(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::BuildRepo(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchBuildRepo(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::CancelRepoBuild(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StartContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RestartContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PauseContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::UnpauseContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StopContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DestroyContainer(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StartAllContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RestartAllContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PauseAllContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::UnpauseAllContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StopAllContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneContainers(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DeleteNetwork(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneNetworks(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DeleteImage(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneImages(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DeleteVolume(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneVolumes(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneDockerBuilders(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneBuildx(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PruneSystem(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RunSync(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::CommitSync(request) => client
.write(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DeployStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchDeployStack(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::DeployStackIfChanged(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchDeployStackIfChanged(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::PullStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchPullStack(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::StartStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RestartStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::PauseStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::UnpauseStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::StopStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::DestroyStack(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::BatchDestroyStack(request) => {
client.execute(request).await.map(ExecutionResult::Batch)
}
Execution::RunStackService(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::TestAlerter(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::SendAlert(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RemoveSwarmNodes(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RemoveSwarmStacks(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RemoveSwarmServices(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::CreateSwarmConfig(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RotateSwarmConfig(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RemoveSwarmConfigs(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::CreateSwarmSecret(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RotateSwarmSecret(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RemoveSwarmSecrets(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::ClearRepoCache(request) => client
.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::RotateAllServerKeys(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::RotateCoreKeys(request) => client
.execute(request)
.await
.map(|u| ExecutionResult::Single(u.into())),
Execution::Sleep(request) => {
let duration =
Duration::from_millis(request.duration_ms as u64);
tokio::time::sleep(duration).await;
println!("Finished sleeping!");
std::process::exit(0)
}
Execution::None(_) => unreachable!(),
};
match res {
Ok(ExecutionResult::Single(update)) => {
+31 -193
View File
@@ -7,8 +7,7 @@ use komodo_client::{
api::read::{
ListActions, ListAlerters, ListBuilders, ListBuilds,
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
ListSchedules, ListServers, ListStacks, ListSwarms, ListTags,
ListTerminals,
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
},
entities::{
ResourceTargetVariant,
@@ -32,7 +31,6 @@ use komodo_client::{
schedule::Schedule,
server::{ServerListItem, ServerListItemInfo, ServerState},
stack::{StackListItem, StackListItemInfo, StackState},
swarm::{SwarmListItem, SwarmListItemInfo, SwarmState},
sync::{
ResourceSyncListItem, ResourceSyncListItemInfo,
ResourceSyncState,
@@ -54,46 +52,28 @@ 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, list.page, false)
.await
}
Some(ListCommand::Swarms(filters)) => {
list_resources::<SwarmListItem>(filters, list.page, false).await
list_resources::<ServerListItem>(filters, false).await
}
Some(ListCommand::Stacks(filters)) => {
list_resources::<StackListItem>(filters, list.page, false).await
list_resources::<StackListItem>(filters, false).await
}
Some(ListCommand::Deployments(filters)) => {
list_resources::<DeploymentListItem>(filters, list.page, false)
.await
list_resources::<DeploymentListItem>(filters, false).await
}
Some(ListCommand::Builds(filters)) => {
list_resources::<BuildListItem>(filters, list.page, false).await
list_resources::<BuildListItem>(filters, false).await
}
Some(ListCommand::Repos(filters)) => {
list_resources::<RepoListItem>(filters, list.page, false).await
list_resources::<RepoListItem>(filters, false).await
}
Some(ListCommand::Procedures(filters)) => {
list_resources::<ProcedureListItem>(filters, list.page, false)
.await
list_resources::<ProcedureListItem>(filters, false).await
}
Some(ListCommand::Actions(filters)) => {
list_resources::<ActionListItem>(filters, list.page, false)
.await
list_resources::<ActionListItem>(filters, false).await
}
Some(ListCommand::Syncs(filters)) => {
list_resources::<ResourceSyncListItem>(
filters, list.page, false,
)
.await
}
Some(ListCommand::Builders(filters)) => {
list_resources::<BuilderListItem>(filters, list.page, false)
.await
}
Some(ListCommand::Alerters(filters)) => {
list_resources::<AlerterListItem>(filters, list.page, false)
.await
list_resources::<ResourceSyncListItem>(filters, false).await
}
Some(ListCommand::Terminals(filters)) => {
list_terminals(filters).await
@@ -101,6 +81,12 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
Some(ListCommand::Schedules(filters)) => {
list_schedules(filters).await
}
Some(ListCommand::Builders(filters)) => {
list_resources::<BuilderListItem>(filters, false).await
}
Some(ListCommand::Alerters(filters)) => {
list_resources::<AlerterListItem>(filters, false).await
}
}
}
@@ -111,7 +97,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
let (
tags,
mut servers,
mut swarms,
mut stacks,
mut deployments,
mut builds,
@@ -124,15 +109,14 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
.into_iter()
.map(|t| (t.id, t.name))
.collect::<HashMap<_, _>>())),
ServerListItem::list(client, &filters, list.page, true),
SwarmListItem::list(client, &filters, list.page, true),
StackListItem::list(client, &filters, list.page, true),
DeploymentListItem::list(client, &filters, list.page, true),
BuildListItem::list(client, &filters, list.page, true),
RepoListItem::list(client, &filters, list.page, true),
ProcedureListItem::list(client, &filters, list.page, true),
ActionListItem::list(client, &filters, list.page, true),
ResourceSyncListItem::list(client, &filters, list.page, true),
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),
)?;
if !servers.is_empty() {
@@ -141,12 +125,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
println!();
}
if !swarms.is_empty() {
fix_tags(&mut swarms, &tags);
print_items(swarms, filters.format, list.links)?;
println!();
}
if !stacks.is_empty() {
fix_tags(&mut stacks, &tags);
print_items(stacks, filters.format, list.links)?;
@@ -194,7 +172,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
async fn list_resources<T>(
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<()>
where
@@ -203,7 +180,7 @@ where
{
let client = crate::command::komodo_client().await?;
let (mut resources, tags) = tokio::try_join!(
T::list(client, filters, page, minimal),
T::list(client, filters, minimal),
client.read(ListTags::default()).map(|res| res.map(|res| res
.into_iter()
.map(|t| (t.id, t.name))
@@ -228,8 +205,6 @@ async fn list_terminals(
.read(ListTerminals {
target: None,
use_names: true,
limit: Some(0),
..Default::default()
})
.await?;
if !terminals.is_empty() {
@@ -247,8 +222,6 @@ async fn list_schedules(
.read(ListSchedules {
tags: filters.tags.clone(),
tag_behavior: Default::default(),
limit: Some(0),
..Default::default()
})
.map(|res| res.map(|res| res
.into_iter()
@@ -306,7 +279,6 @@ where
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
// For use with root `km ls`
minimal: bool,
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
@@ -319,7 +291,6 @@ impl ListResources for ServerListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let servers = client
@@ -329,10 +300,6 @@ impl ListResources for ServerListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?;
let names = parse_wildcards(&filters.names);
@@ -362,68 +329,17 @@ impl ListResources for ServerListItem {
}
}
impl ListResources for SwarmListItem {
type Info = SwarmListItemInfo;
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let servers = client
.read(ListSwarms {
query: ResourceQuery::builder()
.tags(filters.tags.clone())
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?;
let names = parse_wildcards(&filters.names);
let server_wildcards = parse_wildcards(&filters.servers);
let mut servers = servers
.into_iter()
.filter(|server| {
let state_check = if filters.all {
true
} else if filters.down {
!matches!(server.info.state, SwarmState::Healthy)
} else if filters.in_progress {
false
} else {
matches!(server.info.state, SwarmState::Healthy)
};
let name_items = &[server.name.as_str()];
state_check
&& matches_wildcards(&names, name_items)
&& matches_wildcards(&server_wildcards, name_items)
})
.collect::<Vec<_>>();
servers.sort_by(|a, b| {
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
});
Ok(servers)
}
}
impl ListResources for StackListItem {
type Info = StackListItemInfo;
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (servers, mut stacks) = tokio::try_join!(
client
.read(ListServers {
query: ResourceQuery::builder().build(),
limit: Some(0),
..Default::default()
})
.map(|res| res.map(|res| res
.into_iter()
@@ -435,10 +351,6 @@ impl ListResources for StackListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false
})
)?;
stacks.iter_mut().for_each(|stack| {
@@ -494,15 +406,12 @@ impl ListResources for DeploymentListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
_minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (servers, mut deployments) = tokio::try_join!(
client
.read(ListServers {
query: ResourceQuery::builder().build(),
limit: Some(0),
..Default::default()
})
.map(|res| res.map(|res| res
.into_iter()
@@ -514,10 +423,6 @@ impl ListResources for DeploymentListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false
})
)?;
deployments.iter_mut().for_each(|deployment| {
@@ -574,15 +479,12 @@ impl ListResources for BuildListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let (builders, mut builds) = tokio::try_join!(
client
.read(ListBuilders {
query: ResourceQuery::builder().build(),
limit: Some(0),
..Default::default()
})
.map(|res| res.map(|res| res
.into_iter()
@@ -594,10 +496,6 @@ impl ListResources for BuildListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false
})
)?;
builds.iter_mut().for_each(|build| {
@@ -635,7 +533,10 @@ impl ListResources for BuildListItem {
})
.collect::<Vec<_>>();
builds.sort_by(|a, b| {
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
a.name
.cmp(&b.name)
.then(a.info.builder_id.cmp(&b.info.builder_id))
.then(a.info.state.cmp(&b.info.state))
});
Ok(builds)
}
@@ -646,7 +547,6 @@ impl ListResources for RepoListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -657,10 +557,6 @@ impl ListResources for RepoListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -699,7 +595,6 @@ impl ListResources for ProcedureListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -710,10 +605,6 @@ impl ListResources for ProcedureListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -741,7 +632,7 @@ impl ListResources for ProcedureListItem {
(Some(a), Some(b)) => return a.cmp(&b),
(None, None) => {}
}
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
});
Ok(procedures)
}
@@ -752,7 +643,6 @@ impl ListResources for ActionListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -763,10 +653,6 @@ impl ListResources for ActionListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -794,7 +680,7 @@ impl ListResources for ActionListItem {
(Some(a), Some(b)) => return a.cmp(&b),
(None, None) => {}
}
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
});
Ok(actions)
}
@@ -805,7 +691,6 @@ impl ListResources for ResourceSyncListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -816,10 +701,6 @@ impl ListResources for ResourceSyncListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -844,7 +725,7 @@ impl ListResources for ResourceSyncListItem {
})
.collect::<Vec<_>>();
syncs.sort_by(|a, b| {
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
});
Ok(syncs)
}
@@ -855,7 +736,6 @@ impl ListResources for BuilderListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -866,10 +746,6 @@ impl ListResources for BuilderListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -892,7 +768,6 @@ impl ListResources for AlerterListItem {
async fn list(
client: &KomodoClient,
filters: &ResourceFilters,
page: u64,
minimal: bool,
) -> anyhow::Result<Vec<Self>> {
let names = parse_wildcards(&filters.names);
@@ -903,10 +778,6 @@ impl ListResources for AlerterListItem {
// .tag_behavior(TagQueryBehavior::Any)
.templates(filters.templates)
.build(),
limit: None,
page: page.saturating_sub(1),
sort_by: Default::default(),
sort_desc: false,
})
.await?
.into_iter()
@@ -961,39 +832,6 @@ impl PrintTable for ResourceListItem<ServerListItemInfo> {
}
}
impl PrintTable for ResourceListItem<SwarmListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
&["Swarm", "State", "Tags", "Link"]
} else {
&["Swarm", "State", "Tags"]
}
}
fn row(self, links: bool) -> Vec<Cell> {
let color = match self.info.state {
SwarmState::Healthy => Color::Green,
SwarmState::Unhealthy => Color::Red,
SwarmState::Down => Color::Blue,
SwarmState::Unknown => Color::Magenta,
};
let mut res = 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(
&cli_config().host,
ResourceTargetVariant::Server,
&self.id,
)))
}
res
}
}
impl PrintTable for ResourceListItem<StackListItemInfo> {
fn header(links: bool) -> &'static [&'static str] {
if links {
-2
View File
@@ -15,8 +15,6 @@ use wildcard::Wildcard;
use crate::config::cli_config;
pub mod container;
pub mod core_info;
pub mod create;
pub mod database;
pub mod execute;
pub mod list;
+44 -99
View File
@@ -2,7 +2,7 @@ use anyhow::{Context, anyhow};
use colored::Colorize;
use komodo_client::{
api::{
read::{GetServer, ListAllContainers, ListServers},
read::{ListAllDockerContainers, ListServers},
terminal::InitTerminal,
},
entities::{
@@ -26,7 +26,7 @@ pub async fn handle_connect(
recreate,
}: &Connect,
) -> anyhow::Result<()> {
handle_terminal_forwarding(server, async {
handle_terminal_forwarding(async {
super::komodo_client()
.await?
.connect_server_terminal(
@@ -56,28 +56,25 @@ pub async fn handle_exec(
}: &Exec,
) -> anyhow::Result<()> {
let server = get_server(server.clone(), container).await?;
handle_terminal_forwarding(
&format!("{server}/{container}"),
async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: Some(shell.to_string()),
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Exec),
}),
)
.await
},
)
handle_terminal_forwarding(async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: Some(shell.to_string()),
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Exec),
}),
)
.await
})
.await
}
@@ -89,28 +86,25 @@ pub async fn handle_attach(
}: &Attach,
) -> anyhow::Result<()> {
let server = get_server(server.clone(), container).await?;
handle_terminal_forwarding(
&format!("{server}/{container}-attach"),
async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: None,
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Attach),
}),
)
.await
},
)
handle_terminal_forwarding(async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: None,
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Attach),
}),
)
.await
})
.await
}
@@ -118,8 +112,6 @@ async fn get_server(
server: Option<String>,
container: &str,
) -> anyhow::Result<String> {
// Don't need to find server for terminal,
// user provided it, early return.
if let Some(server) = server {
return Ok(server);
}
@@ -127,15 +119,9 @@ async fn get_server(
let client = super::komodo_client().await?;
let mut containers = client
.read(ListAllContainers {
.read(ListAllDockerContainers {
servers: Default::default(),
tags: Default::default(),
terms: vec![container.to_string()],
state: Default::default(),
limit: Some(0),
page: 0,
sort_by: Default::default(),
sort_desc: false,
containers: vec![container.to_string()],
})
.await?;
@@ -146,14 +132,11 @@ async fn get_server(
}
if containers.len() == 1 {
let server_id = containers
return containers
.pop()
.context("Shouldn't happen")?
.server_id
.context("Container doesn't have server_id")?;
let server_name =
client.read(GetServer { server: server_id }).await?.name;
return Ok(server_name);
.context("Container doesn't have server_id");
}
let servers = containers
@@ -164,8 +147,6 @@ async fn get_server(
let servers = client
.read(ListServers {
query: ServerQuery::builder().names(servers).build(),
limit: Some(0),
..Default::default()
})
.await?
.into_iter()
@@ -182,7 +163,6 @@ async fn get_server(
async fn handle_terminal_forwarding<
C: Future<Output = anyhow::Result<TerminalWebsocket>>,
>(
label: &str,
connect: C,
) -> anyhow::Result<()> {
// Need to forward multiple sources into ws write
@@ -267,16 +247,6 @@ async fn handle_terminal_forwarding<
let forward_read = async {
let mut stdout = tokio::io::stdout();
// Write connection message
if let Err(e) = write_connection_message(&mut stdout, label)
.await
.context("Failed to write text to stdout")
{
cancel.cancel();
return Some(e);
}
while let Some(msg) =
future_or_cancel(ws_read.receive_stdout(), &cancel).await
{
@@ -327,31 +297,6 @@ async fn handle_terminal_forwarding<
std::process::exit(0)
}
async fn write_connection_message(
stdout: &mut tokio::io::Stdout,
label: &str,
) -> anyhow::Result<()> {
// Use message without ansi for correct length
let message_clean = format!("# Connected to {label} (km) #");
let bounder = "=".repeat(message_clean.chars().count());
let message = format!(
"# {} to {} {} #",
"Connected".green().bold(),
label.bold(),
"(km)".dimmed()
);
stdout
.write_all(
format!("\n{bounder}\r\n{message}\r\n{bounder}\r\n").as_bytes(),
)
.await?;
let _ = stdout.flush().await;
Ok(())
}
fn resize_message() -> anyhow::Result<TerminalStdinMessage> {
let (cols, rows) = crossterm::terminal::size()
.context("Failed to get terminal size")?;
-3
View File
@@ -264,9 +264,6 @@ pub fn cli_config() -> &'static CliConfig {
ansi: env
.komodo_cli_logging_ansi
.unwrap_or(config.cli_logging.ansi),
timestamps: env
.komodo_cli_logging_timestamps
.unwrap_or(config.cli_logging.timestamps),
otlp_endpoint: env
.komodo_cli_logging_otlp_endpoint
.unwrap_or(config.cli_logging.otlp_endpoint),
-7
View File
@@ -12,9 +12,6 @@ mod config;
async fn app() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("Failed to install default crypto provider");
mogh_logger::init(&config::cli_config().cli_logging)?;
let args = config::cli_args();
let env = config::cli_env();
@@ -45,7 +42,6 @@ async fn app() -> anyhow::Result<()> {
}
Ok(())
}
args::Command::CoreInfo => command::core_info::handle().await,
args::Command::Container(container) => {
command::container::handle(container).await
}
@@ -56,9 +52,6 @@ async fn app() -> anyhow::Result<()> {
args::Command::Execute(args) => {
command::execute::handle(&args.execution, args.yes).await
}
args::Command::Create { command } => {
command::create::handle(command).await
}
args::Command::Update { command } => {
command::update::handle(command).await
}
+1
View File
@@ -2,6 +2,7 @@
name = "komodo_core"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
+2 -4
View File
@@ -1,7 +1,7 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
# Build Core
FROM rust:1.97.1-trixie AS core-builder
FROM rust:1.93.1-trixie AS core-builder
RUN cargo install cargo-strip
WORKDIR /builder
@@ -11,7 +11,6 @@ COPY ./client/core/rs ./client/core/rs
COPY ./client/periphery ./client/periphery
COPY ./bin/core ./bin/core
COPY ./bin/cli ./bin/cli
COPY ./xtask ./xtask
# Compile app
RUN cargo build -p komodo_core --release && \
@@ -59,8 +58,7 @@ ENV KOMODO_CLI_CONFIG_PATHS="/config"
# This ensures any `komodo.cli.*` takes precedence over the Core `/config/*config.*`
ENV KOMODO_CLI_CONFIG_KEYWORDS="*config.*,*komodo.cli*.*"
ENTRYPOINT [ "entrypoint.sh" ]
CMD [ "core" ]
CMD [ "/bin/bash", "-c", "update-ca-certificates && core" ]
# Label to prevent Komodo from stopping with StopAllContainers
LABEL komodo.skip="true"
+2 -2
View File
@@ -2,8 +2,8 @@
## Sets up the necessary runtime container dependencies for Komodo Core.
## Since theres no heavy build here, QEMU multi-arch builds are fine for this image.
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG UI_IMAGE=ghcr.io/moghtech/komodo-ui:2
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
ARG UI_IMAGE=ghcr.io/moghtech/komodo-ui:latest
ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64
ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
+1 -1
View File
@@ -1,7 +1,7 @@
## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile).
## Sets up the necessary runtime container dependencies for Komodo Core.
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
# This is required to work with COPY --from
FROM ${BINARIES_IMAGE} AS binaries
+30 -32
View File
@@ -20,14 +20,12 @@ pub async fn send_alert(
let link = resource_link(ResourceTargetVariant::Swarm, id);
match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | Swarm **{name}** is now **healthy**\n{link}"
)
format!("{level} | Swarm **{name}** is now **healthy**\n{link}")
}
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\n**error**: {e:#?}"))
.map(|e| format!("\n**error**: {e}"))
.unwrap_or_default();
format!(
"{level} | Swarm **{name}** is **unhealthy** ❌\n{link}{err}"
@@ -36,6 +34,28 @@ pub async fn send_alert(
_ => unreachable!(),
}
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | **{name}**{region} | Periphery version now matches Core version ✅\n{link}"
)
}
_ => {
format!(
"{level} | **{name}**{region} | Version mismatch detected ⚠️\nPeriphery: **{server_version}** | Core: **{core_version}**\n{link}"
)
}
}
}
AlertData::ServerUnreachable {
id,
name,
@@ -62,28 +82,6 @@ pub async fn send_alert(
_ => unreachable!(),
}
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | **{name}**{region} | Periphery version now matches Core version ✅\n{link}"
)
}
_ => {
format!(
"{level} | **{name}**{region} | Version mismatch detected ⚠️\nPeriphery: **{server_version}** | Core: **{core_version}**\n{link}"
)
}
}
}
AlertData::ServerCpu {
id,
name,
@@ -142,7 +140,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"📦 Deployment **{name}** is now **{to}**{target}\nprevious: **{from}**\n{link}"
@@ -163,7 +161,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"⬆ Deployment **{name}** has an update available{target}\nimage: **{image}**\n{link}"
@@ -184,7 +182,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"⬆ Deployment **{name}** was updated automatically ⏫{target}\nimage: **{image}**\n{link}"
@@ -207,7 +205,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"🥞 Stack **{name}** is now {to}{target}\nprevious: **{from}**\n{link}"
@@ -229,7 +227,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"⬆ Stack **{name}** has an update available{target}\nservice: **{service}**\nimage: **{image}**\n{link}"
@@ -253,7 +251,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
format!("")
};
format!(
"⬆ Stack **{name}** was updated automatically ⏫{target}\n{images_label}: **{images}**\n{link}"
+32 -34
View File
@@ -260,35 +260,9 @@ fn standard_alert_content(alert: &Alert) -> String {
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.map(|e| format!("\nerror: {e}"))
.unwrap_or_default();
format!(
"{level} | Swarm {name} is unhealthy ❌\n{link}{err}"
)
}
_ => unreachable!(),
}
}
AlertData::ServerUnreachable {
id,
name,
region,
err,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
match alert.level {
SeverityLevel::Ok => {
format!("{level} | {name}{region} is now connected\n{link}")
}
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
format!(
"{level} | {name}{region} is unreachable ❌\n{link}{err}"
)
format!("{level} | Swarm {name} is unhealthy ❌\n{link}{err}")
}
_ => unreachable!(),
}
@@ -315,6 +289,30 @@ fn standard_alert_content(alert: &Alert) -> String {
}
}
}
AlertData::ServerUnreachable {
id,
name,
region,
err,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
match alert.level {
SeverityLevel::Ok => {
format!("{level} | {name}{region} is now connected\n{link}")
}
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
format!(
"{level} | {name}{region} is unreachable ❌\n{link}{err}"
)
}
_ => unreachable!(),
}
}
AlertData::ServerCpu {
id,
name,
@@ -373,7 +371,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"📦Deployment {name} is now {to_state}{target}\nprevious: {from}\n{link}",
@@ -394,7 +392,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"⬆ Deployment {name} has an update available{target}\nimage: {image}\n{link}",
@@ -415,7 +413,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"⬆ Deployment {name} was updated automatically{target}\nimage: {image}\n{link}",
@@ -438,7 +436,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"🥞 Stack {name} is now {to_state}{target}\nprevious: {from}\n{link}",
@@ -460,7 +458,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"⬆ Stack {name} has an update available{target}\nservice: {service}\nimage: {image}\n{link}",
@@ -484,7 +482,7 @@ fn standard_alert_content(alert: &Alert) -> String {
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
format!("")
};
format!(
"⬆ Stack {name} was updated automatically ⏫{target}\n{images_label}: {images_str}\n{link}",
+36 -36
View File
@@ -42,7 +42,7 @@ pub async fn send_alert(
format!("{level} | Swarm *{name}* is *unhealthy* ❌");
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.map(|e| format!("\nerror: {e}"))
.unwrap_or_default();
let blocks = vec![
Block::header(level),
@@ -59,6 +59,35 @@ pub async fn send_alert(
_ => unreachable!(),
}
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let text = match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
)
}
_ => {
format!(
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
)
}
};
let blocks = vec![
Block::header(text.clone()),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
(text, blocks.into())
}
AlertData::ServerUnreachable {
id,
name,
@@ -100,35 +129,6 @@ pub async fn send_alert(
_ => unreachable!(),
}
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let text = match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
)
}
_ => {
format!(
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
)
}
};
let blocks = vec![
Block::header(text.clone()),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
(text, blocks.into())
}
AlertData::ServerCpu {
id,
name,
@@ -289,7 +289,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
@@ -317,7 +317,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
@@ -345,7 +345,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
@@ -374,7 +374,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
@@ -402,7 +402,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
@@ -435,7 +435,7 @@ pub async fn send_alert(
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
format!("")
};
let blocks = vec![
Block::header(text.clone()),
+18 -105
View File
@@ -4,26 +4,23 @@ use std::{
sync::OnceLock,
};
use anyhow::Context as _;
use command::{CommandOptions, run_komodo_standard_command};
use database::{
bson::doc,
mungos::{by_id::update_one_by_id, mongodb::bson::to_document},
use anyhow::Context;
use command::run_komodo_standard_command;
use database::mungos::{
by_id::update_one_by_id, mongodb::bson::to_document,
};
use interpolate::Interpolator;
use komodo_client::{
api::execute::{
BatchExecutionResponse, BatchRunAction, CancelAction, RunAction,
},
api::execute::{BatchExecutionResponse, BatchRunAction, RunAction},
entities::{
FileFormat, JsonObject, Operation,
FileFormat, JsonObject,
action::Action,
alert::{Alert, AlertData, SeverityLevel},
config::core::CoreConfig,
komodo_timestamp,
permission::PermissionLevel,
random_string,
update::{Update, UpdateStatus},
update::Update,
user::action_user,
},
parsers::parse_key_value_list,
@@ -37,7 +34,6 @@ use mogh_auth_server::api::manage::api_key::{
use mogh_config::merge_objects;
use mogh_resolver::Resolve;
use tokio::fs;
use tokio_util::sync::CancellationToken;
use crate::{
alert::send_alerts,
@@ -50,7 +46,7 @@ use crate::{
},
permission::get_check_permissions,
resource::refresh_action_state_cache,
state::{action_cancel_cache, action_states, db_client},
state::{action_states, db_client},
};
use super::ExecuteArgs;
@@ -70,23 +66,18 @@ impl Resolve<ExecuteArgs> for BatchRunAction {
"BatchRunAction",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunAction>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchRunAction>(&self.pattern, user)
.await?,
)
}
}
@@ -96,7 +87,7 @@ impl Resolve<ExecuteArgs> for RunAction {
"RunAction",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
action = self.action,
@@ -104,11 +95,7 @@ impl Resolve<ExecuteArgs> for RunAction {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut action = get_check_permissions::<Action>(
&self.action,
@@ -125,7 +112,7 @@ impl Resolve<ExecuteArgs> for RunAction {
// This will set action state back to default when dropped.
// Will also check to ensure action not already busy before updating.
let action_guard = action_state.update_custom(
let _action_guard = action_state.update_custom(
|state| state.running += 1,
|state| state.running -= 1,
false,
@@ -206,20 +193,14 @@ impl Resolve<ExecuteArgs> for RunAction {
""
};
let cancel = CancellationToken::new();
action_cancel_cache()
.insert(update.id.clone(), cancel.clone())
.await;
let mut res = run_komodo_standard_command(
// Keep this stage name as is, the UI will find the latest update log by matching the stage name
"Execute Action",
None,
format!(
"deno run --allow-all{https_cert_flag}{reload} {}",
path.display()
),
CommandOptions::default().cancel(cancel),
)
.await;
@@ -233,7 +214,7 @@ impl Resolve<ExecuteArgs> for RunAction {
update.logs.push(res);
update.finalize();
mogh_error::Ok(())
mogh_error::Ok(update)
}
.await;
@@ -246,13 +227,7 @@ impl Resolve<ExecuteArgs> for RunAction {
);
};
action_cancel_cache().remove(&update.id).await;
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
res?;
let update = res?;
// Need to manually update the update before cache refresh,
// and before broadcast with update_update.
@@ -480,65 +455,3 @@ fn parse_action_arguments(
.context("Failed to parse Json to action args"),
}
}
impl Resolve<ExecuteArgs> for CancelAction {
#[instrument(
"CancelAction",
skip_all,
fields(
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
action = self.action,
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let action = get_check_permissions::<Action>(
&self.action,
user,
PermissionLevel::Execute.into(),
)
.await?;
let update_id = if let Some(update_id) = self.update_id
&& !update_id.is_empty()
{
update_id
} else {
db_client()
.updates
.find_one(doc! {
"target.type": "Action",
"target.id": &action.id,
"operation": Operation::RunAction.as_ref(),
"status": UpdateStatus::InProgress.as_ref(),
})
.await?
.context("No active run to cancel found")?
.id
};
action_cancel_cache()
.get(&update_id)
.await
.context("Action run cancel token not found")?
.cancel();
let mut update = update.clone();
update.push_simple_log(
"Cancel Triggered",
"The action cancel has been triggered.",
);
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
+4 -14
View File
@@ -28,7 +28,7 @@ impl Resolve<ExecuteArgs> for TestAlerter {
"TestAlerter",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
alerter = self.alerter,
@@ -36,11 +36,7 @@ impl Resolve<ExecuteArgs> for TestAlerter {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let alerter = get_check_permissions::<Alerter>(
&self.alerter,
@@ -98,7 +94,7 @@ impl Resolve<ExecuteArgs> for SendAlert {
"SendAlert",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
request = format!("{self:?}"),
@@ -106,16 +102,10 @@ impl Resolve<ExecuteArgs> for SendAlert {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let alerters = list_full_for_user::<Alerter>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+34 -55
View File
@@ -28,7 +28,7 @@ use komodo_client::{
alert::{Alert, AlertData, SeverityLevel},
all_logs_success,
build::{Build, BuildConfig},
builder::Builder,
builder::{Builder, BuilderConfig},
deployment::DeploymentState,
komodo_timestamp, optional_string,
permission::PermissionLevel,
@@ -75,23 +75,18 @@ impl Resolve<ExecuteArgs> for BatchRunBuild {
"BatchRunBuild",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunBuild>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchRunBuild>(&self.pattern, user)
.await?,
)
}
}
@@ -101,7 +96,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
"RunBuild",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
build = self.build,
@@ -109,11 +104,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut build = get_check_permissions::<Build>(
&self.build,
@@ -153,7 +144,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
// This will set action state back to default when dropped.
// Will also check to ensure build not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.building = true)?;
if build.config.auto_increment_version {
@@ -180,6 +171,9 @@ impl Resolve<ExecuteArgs> for RunBuild {
let builder =
resource::get::<Builder>(&build.config.builder_id).await?;
let is_server_builder =
matches!(&builder.config, BuilderConfig::Server(_));
tokio::spawn(async move {
let poll = async {
loop {
@@ -188,13 +182,19 @@ impl Resolve<ExecuteArgs> for RunBuild {
id = cancel_recv.recv() => id?
};
if incoming_build_id == build_id {
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
if is_server_builder {
update.push_error_log("Cancel acknowledged", "Build cancellation is not possible on server builders at this time. Use an AWS builder to enable this feature.");
} else {
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
}
update.finalize();
let id = update.id.clone();
if let Err(e) = update_update(update).await {
warn!("Failed to modify Update {id} on db | {e:#}");
}
cancel_clone.cancel();
if !is_server_builder {
cancel_clone.cancel();
}
return Ok(());
}
}
@@ -212,7 +212,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
build.name.clone(),
Some(build.config.version),
builder,
Some(&mut update),
&mut update,
)
.await
{
@@ -269,11 +269,11 @@ impl Resolve<ExecuteArgs> for RunBuild {
replacers: Default::default(),
}) => res,
_ = cancel.cancelled() => {
debug!("Build cancelled during repo clone, cleaning up builder");
update.push_error_log("Build cancelled", String::from("Build cancelled during repo clone"));
debug!("Build cancelled during clone, cleaning up builder");
update.push_error_log("Build cancelled", String::from("user cancelled build during repo clone"));
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
debug!("Builder cleaned up");
info!("Builder cleaned up");
return handle_early_return(update, build.id, build.name, true).await
},
};
@@ -319,14 +319,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
}) => res.context("Failed at call to Periphery to build"),
_ = cancel.cancelled() => {
info!("Build cancelled during build, cleaning up builder");
if let Err(e) = periphery.request(api::build::CancelBuild {
id: build.id.clone()
})
.await
.context("Failed to cancel build execution on Server") {
update.push_error_log("Cancel Build", format_serror(&e.into()));
}
update.push_error_log("Build Cancelled", String::from("User cancelled build during image build step"));
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
return handle_early_return(update, build.id, build.name, true).await
@@ -376,10 +369,6 @@ impl Resolve<ExecuteArgs> for RunBuild {
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
// The Err case of to_document should be unreachable,
@@ -516,10 +505,10 @@ pub async fn validate_cancel_build(
)?;
match (latest_build, latest_cancel) {
(Some(build), Some(cancel))
if cancel.start_ts > build.start_ts =>
{
return Err(anyhow!("Build has already been cancelled"));
(Some(build), Some(cancel)) => {
if cancel.start_ts > build.start_ts {
return Err(anyhow!("Build has already been cancelled"));
}
}
(None, _) => return Err(anyhow!("No build in progress")),
_ => {}
@@ -533,7 +522,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
"CancelBuild",
skip(user, update),
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
build = self.build,
@@ -541,11 +530,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let build = get_check_permissions::<Build>(
&self.build,
@@ -568,8 +553,8 @@ impl Resolve<ExecuteArgs> for CancelBuild {
let mut update = update.clone();
update.push_simple_log(
"Cancel Triggered",
"The build cancel has been triggered",
"cancel triggered",
"the build cancel has been triggered",
);
update_update(update.clone()).await?;
@@ -624,13 +609,7 @@ async fn handle_post_build_redeploy(build_id: &str) {
let state = get_deployment_state(&deployment.id)
.await
.unwrap_or_default();
if ![
DeploymentState::NotDeployed,
DeploymentState::Exited,
DeploymentState::Unknown,
]
.contains(&state)
{
if state == DeploymentState::Running {
let req = super::ExecuteRequest::Deploy(Deploy {
deployment: deployment.id.clone(),
stop_signal: None,
@@ -647,7 +626,7 @@ async fn handle_post_build_redeploy(build_id: &str) {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
}
+61 -156
View File
@@ -32,7 +32,7 @@ use crate::{
swarm::swarm_request,
update::update_update,
},
monitor::{refresh_server_cache, refresh_swarm_cache},
monitor::{update_cache_for_server, update_cache_for_swarm},
resource::{self, setup_deployment_execution},
state::action_states,
};
@@ -55,23 +55,18 @@ impl Resolve<ExecuteArgs> for BatchDeploy {
"BatchDeploy",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeploy>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchDeploy>(&self.pattern, user)
.await?,
)
}
}
@@ -81,7 +76,7 @@ impl Resolve<ExecuteArgs> for Deploy {
"Deploy",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -91,11 +86,7 @@ impl Resolve<ExecuteArgs> for Deploy {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut deployment, swarm_or_server) =
setup_deployment_execution(
@@ -115,7 +106,7 @@ impl Resolve<ExecuteArgs> for Deploy {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.deploying = true)?;
let mut update = update.clone();
@@ -127,16 +118,26 @@ impl Resolve<ExecuteArgs> for Deploy {
let (version, registry_token) = match &deployment.config.image {
DeploymentImage::Build { build_id, version } => {
let build = resource::get::<Build>(build_id).await?;
let image_names = build.get_image_names();
let image_name = image_names
.first()
.context("No image name could be created")
.context("Failed to create image name")?;
let version = if version.is_none() {
build.config.version
} else {
*version
};
let image_name = build.get_deployment_image_name();
let image_tag = build.get_deployment_image_tag(version);
let version_str = version.to_string();
// Potentially add the build image_tag postfix
let version_str = if build.config.image_tag.is_empty() {
version_str
} else {
format!("{version_str}-{}", build.config.image_tag)
};
// replace image with corresponding build image.
deployment.config.image = DeploymentImage::Image {
image: format!("{image_name}:{image_tag}"),
image: format!("{image_name}:{version_str}"),
};
let first_registry = build
.config
@@ -206,12 +207,6 @@ impl Resolve<ExecuteArgs> for Deploy {
update_update(update.clone()).await?;
let deployment_id = deployment.id.clone();
// Track the name the container / service is deployed under,
// so the Deployment stays matched to it even if the
// name configuration changes before the next deploy.
let fresh_name = deployment.custom_name().to_string();
let prev_deployed_name = deployment.info.deployed_name.clone();
let mut deployed = false;
match swarm_or_server {
SwarmOrServer::None => unreachable!(),
@@ -227,8 +222,7 @@ impl Resolve<ExecuteArgs> for Deploy {
.await
{
Ok(logs) => {
refresh_swarm_cache(&swarm, true).await;
deployed = logs.iter().all(|log| log.success);
update_cache_for_swarm(&swarm, true).await;
update.logs.extend(logs)
}
Err(e) => {
@@ -252,8 +246,7 @@ impl Resolve<ExecuteArgs> for Deploy {
.await
{
Ok(log) => {
refresh_server_cache(&server, true).await;
deployed = log.success;
update_cache_for_server(&server, true).await;
update.logs.push(log)
}
Err(e) => {
@@ -270,11 +263,6 @@ impl Resolve<ExecuteArgs> for Deploy {
&deployment_id,
&DeploymentInfo {
latest_image_digest: Default::default(),
deployed_name: if deployed {
fresh_name
} else {
prev_deployed_name
},
},
)
.await
@@ -286,11 +274,6 @@ impl Resolve<ExecuteArgs> for Deploy {
}
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -418,7 +401,7 @@ pub async fn pull_deployment_inner(
Err(e) => Log::error("Pull image", format_serror(&e.into())),
};
refresh_server_cache(server, true).await;
update_cache_for_server(server, true).await;
anyhow::Ok(log)
}
.await;
@@ -435,7 +418,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
"PullDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -443,11 +426,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -471,7 +450,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pulling = true)?;
let mut update = update.clone();
@@ -482,10 +461,6 @@ impl Resolve<ExecuteArgs> for PullDeployment {
update.logs.push(log);
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -497,7 +472,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
"StartDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -505,11 +480,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -533,7 +504,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.starting = true)?;
let mut update = update.clone();
@@ -544,7 +515,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
let log = match periphery_client(&server)
.await?
.request(api::container::StartContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
})
.await
{
@@ -556,12 +527,8 @@ impl Resolve<ExecuteArgs> for StartDeployment {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -573,7 +540,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
"RestartDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -581,11 +548,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -609,7 +572,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.restarting = true)?;
let mut update = update.clone();
@@ -620,7 +583,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
let log = match periphery_client(&server)
.await?
.request(api::container::RestartContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
})
.await
{
@@ -634,12 +597,8 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -651,7 +610,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
"PauseDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -659,11 +618,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -687,7 +642,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pausing = true)?;
let mut update = update.clone();
@@ -698,7 +653,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
let log = match periphery_client(&server)
.await?
.request(api::container::PauseContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
})
.await
{
@@ -710,12 +665,8 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -727,7 +678,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
"UnpauseDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -735,11 +686,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -763,7 +710,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.unpausing = true)?;
let mut update = update.clone();
@@ -774,7 +721,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
let log = match periphery_client(&server)
.await?
.request(api::container::UnpauseContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
})
.await
{
@@ -788,12 +735,8 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -805,7 +748,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
"StopDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -815,11 +758,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -843,7 +782,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.stopping = true)?;
let mut update = update.clone();
@@ -854,7 +793,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
let log = match periphery_client(&server)
.await?
.request(api::container::StopContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
signal: self
.signal
.unwrap_or(deployment.config.termination_signal)
@@ -874,12 +813,8 @@ impl Resolve<ExecuteArgs> for StopDeployment {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -902,20 +837,18 @@ impl Resolve<ExecuteArgs> for BatchDestroyDeployment {
"BatchDestroyDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDestroyDeployment>(
&self.pattern,
self.tags,
user,
)
.await?,
@@ -928,7 +861,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
"DestroyDeployment",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -938,11 +871,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
@@ -961,7 +890,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.destroying = true)?;
let mut update = update.clone();
@@ -975,13 +904,13 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
match swarm_request(
&swarm.config.server_ids,
api::swarm::RemoveSwarmServices {
services: vec![deployment.deployed_name().to_string()],
services: vec![deployment.name],
},
)
.await
{
Ok(log) => {
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
log
}
Err(e) => Log::error(
@@ -996,7 +925,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
match periphery_client(&server)
.await?
.request(api::container::RemoveContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
signal: self
.signal
.unwrap_or(deployment.config.termination_signal)
@@ -1009,7 +938,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
.await
{
Ok(log) => {
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
log
}
Err(e) => Log::error(
@@ -1022,33 +951,9 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
}
};
// Clear the tracked deployed name once the container / service
// is confirmed removed, so the next deploy uses the fresh name.
if log.success
&& let Err(e) = resource::update_info::<Deployment>(
&deployment.id,
&DeploymentInfo {
latest_image_digest: deployment
.info
.latest_image_digest
.clone(),
deployed_name: Default::default(),
},
)
.await
{
warn!(
"Failed to clear deployment {} ({}) deployed name after destroy | {e:#}",
deployment.name, deployment.id,
);
}
update.logs.push(log);
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
+51 -113
View File
@@ -1,7 +1,7 @@
use std::{fmt::Write as _, sync::OnceLock};
use anyhow::{Context, anyhow};
use command::{CommandOptions, run_komodo_standard_command};
use command::run_komodo_standard_command;
use database::{
bson::{Document, doc},
mungos::find::find_collect,
@@ -15,7 +15,7 @@ use komodo_client::{
},
entities::{
SwarmOrServer, deployment::DeploymentState, server::ServerState,
stack::StackState, swarm::SwarmState,
stack::StackState,
},
};
use mogh_error::AddStatusCodeError;
@@ -39,7 +39,7 @@ use crate::{
resource::rotate_server_keys,
state::{
db_client, deployment_status_cache, server_status_cache,
stack_status_cache, swarm_status_cache,
stack_status_cache,
},
};
@@ -54,18 +54,14 @@ impl Resolve<ExecuteArgs> for ClearRepoCache {
"ClearRepoCache",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -137,18 +133,14 @@ impl Resolve<ExecuteArgs> for BackupCoreDatabase {
"BackupCoreDatabase",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -167,8 +159,8 @@ impl Resolve<ExecuteArgs> for BackupCoreDatabase {
let res = run_komodo_standard_command(
"Backup Core Database",
None,
"km database backup --yes",
CommandOptions::default(),
)
.await;
@@ -194,18 +186,14 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
"GlobalAutoUpdate",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -244,7 +232,6 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
.context("Failed to query for stacks from database")?;
let server_status_cache = server_status_cache();
let swarm_status_cache = swarm_status_cache();
let stack_status_cache = stack_status_cache();
// Will be edited later at update.logs[0]
@@ -256,7 +243,7 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
continue;
};
// Only check running stacks.
// Only pull running stacks.
if !matches!(status.curr.state, StackState::Running) {
continue;
}
@@ -268,58 +255,43 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
&servers,
)?;
// Ensure server / swarm is reachable
match &swarm_or_server {
SwarmOrServer::None => continue,
SwarmOrServer::Server(server) => {
if !server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
continue;
}
}
SwarmOrServer::Swarm(swarm) => {
if !swarm_status_cache
.get(&swarm.id)
.await
.map(|s| {
matches!(
s.state,
SwarmState::Healthy | SwarmState::Unhealthy
)
})
.unwrap_or_default()
{
continue;
}
}
if let SwarmOrServer::None = &swarm_or_server {
continue;
}
if let Err(e) = check_stack_for_update_inner(
stack.id,
&swarm_or_server,
self.skip_auto_update,
true,
false,
)
.await
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()
{
update.push_error_log(
&format!("Check Stack {}", stack.name),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
}
if let Err(e) = check_stack_for_update_inner(
stack.id,
&swarm_or_server,
self.skip_auto_update,
true,
false,
)
.await
{
update.push_error_log(
&format!("Check Stack {}", 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!(
"Checked Stack {} ✅",
bold(&stack.name)
));
update.logs[0].stdout.push_str(&format!(
"Checked Stack {} ✅",
bold(&stack.name)
));
}
}
}
@@ -336,7 +308,7 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
continue;
};
// Only check running deployments.
// Only pull running deployments.
if !matches!(status.curr.state, DeploymentState::Running) {
continue;
}
@@ -348,34 +320,8 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
&servers,
)?;
// Ensure server / swarm is reachable
match &swarm_or_server {
SwarmOrServer::None => continue,
SwarmOrServer::Server(server) => {
if !server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
continue;
}
}
SwarmOrServer::Swarm(swarm) => {
if !swarm_status_cache
.get(&swarm.id)
.await
.map(|s| {
matches!(
s.state,
SwarmState::Healthy | SwarmState::Unhealthy
)
})
.unwrap_or_default()
{
continue;
}
}
if let SwarmOrServer::None = &swarm_or_server {
continue;
}
let name = deployment.name.clone();
@@ -422,18 +368,14 @@ impl Resolve<ExecuteArgs> for RotateAllServerKeys {
"RotateAllServerKeys",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -540,7 +482,7 @@ impl Resolve<ExecuteArgs> for RotateCoreKeys {
"RotateCoreKeys",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
force = self.force,
@@ -548,11 +490,7 @@ impl Resolve<ExecuteArgs> for RotateCoreKeys {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
+24 -73
View File
@@ -13,7 +13,6 @@ use komodo_client::{
entities::{
Operation,
permission::PermissionLevel,
resource::ResourceQuery,
update::{Log, Update},
user::User,
},
@@ -30,10 +29,7 @@ use uuid::Uuid;
use crate::{
auth::KomodoAuthImpl,
helpers::{
query::get_all_tags,
update::{init_execution_update, update_update},
},
helpers::update::{init_execution_update, update_update},
resource::{KomodoResource, list_full_for_user_using_pattern},
state::db_client,
};
@@ -53,9 +49,9 @@ mod sync;
use super::Variant;
pub struct ExecuteArgs {
/// The task id.
/// Unique for every '/execute' call.
pub task_id: Uuid,
/// The execution id.
/// Unique for every /execute call.
pub id: Uuid,
pub user: User,
pub update: Update,
}
@@ -64,7 +60,7 @@ pub struct ExecuteArgs {
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[strum_discriminants(name(ExecuteRequestMethod), derive(Display))]
#[strum_discriminants(name(ExecuteRequestVariant), derive(Display))]
#[args(ExecuteArgs)]
#[response(JsonString)]
#[error(mogh_error::Error)]
@@ -115,12 +111,10 @@ pub enum ExecuteRequest {
// ==== PROCEDURE ====
RunProcedure(RunProcedure),
BatchRunProcedure(BatchRunProcedure),
CancelProcedure(CancelProcedure),
// ==== ACTION ====
RunAction(RunAction),
BatchRunAction(BatchRunAction),
CancelAction(CancelAction),
// ==== SYNC ====
RunSync(RunSync),
@@ -154,7 +148,6 @@ pub enum ExecuteRequest {
// ==== SWARM ====
RemoveSwarmNodes(RemoveSwarmNodes),
UpdateSwarmNode(UpdateSwarmNode),
RemoveSwarmStacks(RemoveSwarmStacks),
RemoveSwarmServices(RemoveSwarmServices),
CreateSwarmConfig(CreateSwarmConfig),
@@ -256,19 +249,11 @@ pub fn inner_handler(
async move {
let log = match handle.await {
Ok(Err(e)) => {
warn!(
api = "Execute",
task_id = task_id.to_string(),
"/execute request task error: {e:#}",
);
warn!("/execute request {task_id} task error: {e:#}",);
Log::error("Task Error", format_serror(&e.into()))
}
Err(e) => {
warn!(
api = "Execute",
task_id = task_id.to_string(),
"/execute request spawn error: {e:?}",
);
warn!("/execute request {task_id} spawn error: {e:?}",);
Log::error("Spawn Error", format!("{e:#?}"))
}
_ => return,
@@ -283,7 +268,7 @@ pub fn inner_handler(
find_one_by_id(&db_client().updates, &update_id)
.await
.context("Failed to query to db")?
.context("No Update exists with given id")?;
.context("No update exists with given id")?;
update.logs.push(log);
update.finalize();
update_update(update).await
@@ -292,10 +277,7 @@ pub fn inner_handler(
if let Err(e) = res {
warn!(
api = "Execute",
task_id = task_id.to_string(),
update_id,
"Failed to modify Update with task error log | {e:#}"
"Failed to update update with task error log | {e:#}"
);
}
}
@@ -311,44 +293,25 @@ async fn task(
user: User,
update: Update,
) -> anyhow::Result<String> {
let method: ExecuteRequestMethod = (&request).into();
let user_id = user.id.clone();
let username = user.username.clone();
let variant: ExecuteRequestVariant = (&request).into();
info!(
api = "Execute",
task_id = id.to_string(),
method = method.to_string(),
user_id,
username,
"EXECUTE REQUEST",
"EXECUTE REQUEST {id} | METHOD: {variant} | USER: {} ({})",
user.username, user.id
);
let res = match request
.resolve(&ExecuteArgs {
user,
update,
task_id: id,
})
.await
{
Err(e) => Err(e.error),
Ok(JsonString::Err(e)) => Err(
anyhow::Error::from(e).context("failed to serialize response"),
),
Ok(JsonString::Ok(res)) => Ok(res),
};
let res =
match request.resolve(&ExecuteArgs { user, update, id }).await {
Err(e) => Err(e.error),
Ok(JsonString::Err(e)) => Err(
anyhow::Error::from(e)
.context("failed to serialize response"),
),
Ok(JsonString::Ok(res)) => Ok(res),
};
if let Err(e) = &res {
warn!(
api = "Execute",
task_id = id.to_string(),
method = method.to_string(),
user_id,
username,
"EXECUTE REQUEST | ERROR: {e:#}"
);
warn!("EXECUTE REQUEST {id} | METHOD: {variant} | ERROR: {e:#}");
}
res
@@ -362,26 +325,14 @@ trait BatchExecute {
#[instrument("BatchExecute", skip(user))]
async fn batch_execute<E: BatchExecute>(
pattern: &str,
tags: Vec<String>,
user: &User,
) -> anyhow::Result<BatchExecutionResponse> {
let all_tags = if tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let resources = list_full_for_user_using_pattern::<E::Resource>(
pattern,
ResourceQuery {
tags,
..Default::default()
},
None,
None,
Default::default(),
user,
PermissionLevel::Execute.into(),
&all_tags,
&[],
)
.await?;
+9 -76
View File
@@ -1,14 +1,12 @@
use std::pin::Pin;
use anyhow::Context as _;
use database::mungos::{
by_id::update_one_by_id, mongodb::bson::to_document,
};
use formatting::{Color, bold, colored, format_serror, muted};
use komodo_client::{
api::execute::{
BatchExecutionResponse, BatchRunProcedure, CancelProcedure,
RunProcedure,
BatchExecutionResponse, BatchRunProcedure, RunProcedure,
},
entities::{
alert::{Alert, AlertData, SeverityLevel},
@@ -21,14 +19,13 @@ use komodo_client::{
};
use mogh_resolver::Resolve;
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use crate::{
alert::send_alerts,
helpers::{procedure::execute_procedure, update::update_update},
permission::get_check_permissions,
resource::refresh_procedure_state_cache,
state::{action_states, db_client, procedure_cancel_cache},
state::{action_states, db_client},
};
use super::{ExecuteArgs, ExecuteRequest};
@@ -51,12 +48,8 @@ impl Resolve<ExecuteArgs> for BatchRunProcedure {
ExecuteArgs { user, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunProcedure>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchRunProcedure>(&self.pattern, user)
.await?,
)
}
}
@@ -66,7 +59,7 @@ impl Resolve<ExecuteArgs> for RunProcedure {
"RunProcedure",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
procedure = self.procedure,
@@ -74,11 +67,7 @@ impl Resolve<ExecuteArgs> for RunProcedure {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
Ok(
resolve_inner(self.procedure, user.clone(), update.clone())
@@ -124,22 +113,14 @@ fn resolve_inner(
// This will set action state back to default when dropped.
// Will also check to ensure procedure not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.running = true)?;
update_update(update.clone()).await?;
let cancel = CancellationToken::new();
procedure_cancel_cache()
.insert(procedure.id.clone(), cancel.clone())
.await;
let update = Mutex::new(update);
let res = execute_procedure(&procedure, &update, cancel).await;
procedure_cancel_cache().remove(&procedure.id).await;
let res = execute_procedure(&procedure, &update).await;
let mut update = update.into_inner();
@@ -155,15 +136,11 @@ fn resolve_inner(
);
}
Err(e) => update
.push_error_log("Execution error", format_serror(&e.into())),
.push_error_log("execution error", format_serror(&e.into())),
}
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
// The Err case of to_document should be unreachable,
@@ -203,47 +180,3 @@ fn resolve_inner(
Ok(update)
})
}
impl Resolve<ExecuteArgs> for CancelProcedure {
#[instrument(
"CancelProcedure",
skip_all,
fields(
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
procedure = self.procedure,
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let procedure = get_check_permissions::<Procedure>(
&self.procedure,
user,
PermissionLevel::Execute.into(),
)
.await?;
procedure_cancel_cache()
.get(&procedure.id)
.await
.context("Procedure run cancel token not found")?
.cancel();
let mut update = update.clone();
update.push_simple_log(
"Cancel Triggered",
"Procedure cancel has been triggered. The procedure will exit after the currently running stage is complete.",
);
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
+31 -79
View File
@@ -55,23 +55,18 @@ impl Resolve<ExecuteArgs> for BatchCloneRepo {
"BatchCloneRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchCloneRepo>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchCloneRepo>(&self.pattern, user)
.await?,
)
}
}
@@ -81,7 +76,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
"CloneRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -89,11 +84,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
@@ -108,7 +99,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
// This will set action state back to default when dropped.
// Will also check to ensure repo not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.cloning = true)?;
let mut update = update.clone();
@@ -179,10 +170,6 @@ impl Resolve<ExecuteArgs> for CloneRepo {
);
};
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
handle_repo_update_return(update).await
}
}
@@ -199,23 +186,18 @@ impl Resolve<ExecuteArgs> for BatchPullRepo {
"BatchPullRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
pattern = self.pattern
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchPullRepo>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchPullRepo>(&self.pattern, user)
.await?,
)
}
}
@@ -225,7 +207,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
"PullRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -233,11 +215,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
@@ -252,7 +230,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
// This will set action state back to default when dropped.
// Will also check to ensure repo not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.pulling = true)?;
let mut update = update.clone();
@@ -327,10 +305,6 @@ impl Resolve<ExecuteArgs> for PullRepo {
);
};
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
handle_repo_update_return(update).await
}
}
@@ -389,23 +363,18 @@ impl Resolve<ExecuteArgs> for BatchBuildRepo {
"BatchBuildRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchBuildRepo>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchBuildRepo>(&self.pattern, user)
.await?,
)
}
}
@@ -415,7 +384,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
"BuildRepo",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -423,11 +392,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
@@ -446,7 +411,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
// This will set action state back to default when dropped.
// Will also check to ensure repo not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.building = true)?;
let mut update = update.clone();
@@ -513,7 +478,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
repo.name.clone(),
None,
builder,
Some(&mut update),
&mut update,
)
.await
{
@@ -524,9 +489,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
"get builder",
format_serror(&e.context("failed to get builder").into()),
));
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
return handle_builder_early_return(
update, repo.id, repo.name, false,
)
@@ -559,9 +521,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
info!("builder cleaned up");
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
return handle_builder_early_return(update, repo.id, repo.name, true).await
},
};
@@ -609,10 +568,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
// The Err case of to_document should be unreachable,
@@ -731,12 +686,13 @@ pub async fn validate_cancel_repo_build(
)?;
match (latest_build, latest_cancel) {
(Some(build), Some(cancel))
if cancel.start_ts > build.start_ts =>
{
return Err(anyhow!("Repo build has already been cancelled"));
(Some(build), Some(cancel)) => {
if cancel.start_ts > build.start_ts {
return Err(anyhow!(
"Repo build has already been cancelled"
));
}
}
(None, _) => return Err(anyhow!("No repo build in progress")),
_ => {}
};
@@ -749,7 +705,7 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
"CancelRepoBuild",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -757,11 +713,7 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let repo = get_check_permissions::<Repo>(
&self.repo,
+93 -282
View File
@@ -14,7 +14,7 @@ use periphery_client::api;
use crate::{
helpers::{periphery_client, update::update_update},
monitor::refresh_server_cache,
monitor::update_cache_for_server,
permission::get_check_permissions,
state::action_states,
};
@@ -26,7 +26,7 @@ impl Resolve<ExecuteArgs> for StartContainer {
"StartContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -35,11 +35,7 @@ impl Resolve<ExecuteArgs> for StartContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -56,11 +52,8 @@ impl Resolve<ExecuteArgs> for StartContainer {
// Will check to ensure deployment not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.starting_containers += 1,
|state| state.starting_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.starting_containers = true)?;
let mut update = update.clone();
@@ -83,13 +76,9 @@ impl Resolve<ExecuteArgs> for StartContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -101,7 +90,7 @@ impl Resolve<ExecuteArgs> for RestartContainer {
"RestartContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -110,11 +99,7 @@ impl Resolve<ExecuteArgs> for RestartContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -131,11 +116,8 @@ impl Resolve<ExecuteArgs> for RestartContainer {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.restarting_containers += 1,
|state| state.restarting_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.restarting_containers = true)?;
let mut update = update.clone();
@@ -160,13 +142,9 @@ impl Resolve<ExecuteArgs> for RestartContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -178,7 +156,7 @@ impl Resolve<ExecuteArgs> for PauseContainer {
"PauseContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -187,11 +165,7 @@ impl Resolve<ExecuteArgs> for PauseContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -208,11 +182,8 @@ impl Resolve<ExecuteArgs> for PauseContainer {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.pausing_containers += 1,
|state| state.pausing_containers -= 1,
false,
)?;
let _action_guard =
action_state.update(|state| state.pausing_containers = true)?;
let mut update = update.clone();
@@ -235,13 +206,9 @@ impl Resolve<ExecuteArgs> for PauseContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -253,7 +220,7 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
"UnpauseContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -262,11 +229,7 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -283,11 +246,8 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.unpausing_containers += 1,
|state| state.unpausing_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.unpausing_containers = true)?;
let mut update = update.clone();
@@ -312,13 +272,9 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -330,7 +286,7 @@ impl Resolve<ExecuteArgs> for StopContainer {
"StopContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -341,11 +297,7 @@ impl Resolve<ExecuteArgs> for StopContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -362,11 +314,8 @@ impl Resolve<ExecuteArgs> for StopContainer {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.stopping_containers += 1,
|state| state.stopping_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.stopping_containers = true)?;
let mut update = update.clone();
@@ -391,13 +340,9 @@ impl Resolve<ExecuteArgs> for StopContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -409,7 +354,7 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
"DestroyContainer",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -420,11 +365,7 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let DestroyContainer {
server,
@@ -447,11 +388,8 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.destroying_containers += 1,
|state| state.destroying_containers -= 1,
false,
)?;
let _action_guard =
action_state.update(|state| state.pruning_containers = true)?;
let mut update = update.clone();
@@ -478,13 +416,9 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -496,7 +430,7 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
"StartAllContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -504,11 +438,7 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -525,11 +455,8 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.starting_containers += 1,
|state| state.starting_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.starting_containers = true)?;
let mut update = update.clone();
@@ -550,12 +477,8 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
);
}
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -567,7 +490,7 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
"RestartAllContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -575,11 +498,7 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -596,11 +515,8 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.restarting_containers += 1,
|state| state.restarting_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.restarting_containers = true)?;
let mut update = update.clone();
@@ -623,12 +539,8 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
);
}
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -640,7 +552,7 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
"PauseAllContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -648,11 +560,7 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -669,11 +577,8 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.pausing_containers += 1,
|state| state.pausing_containers -= 1,
false,
)?;
let _action_guard =
action_state.update(|state| state.pausing_containers = true)?;
let mut update = update.clone();
@@ -694,12 +599,8 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
);
}
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -711,7 +612,7 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
"UnpauseAllContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -719,11 +620,7 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -740,11 +637,8 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.unpausing_containers += 1,
|state| state.unpausing_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.unpausing_containers = true)?;
let mut update = update.clone();
@@ -767,12 +661,8 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
);
}
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -784,7 +674,7 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
"StopAllContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -792,11 +682,7 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -813,11 +699,8 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard = action_state.update_custom(
|state| state.stopping_containers += 1,
|state| state.stopping_containers -= 1,
false,
)?;
let _action_guard = action_state
.update(|state| state.stopping_containers = true)?;
let mut update = update.clone();
@@ -838,12 +721,8 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
);
}
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -855,7 +734,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
"PruneContainers",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -863,11 +742,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -884,7 +759,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_containers = true)?;
let mut update = update.clone();
@@ -910,13 +785,9 @@ impl Resolve<ExecuteArgs> for PruneContainers {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -928,7 +799,7 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
"DeleteNetwork",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -937,11 +808,7 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -979,7 +846,7 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -993,7 +860,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
"PruneNetworks",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1001,11 +868,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1022,7 +885,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_networks = true)?;
let mut update = update.clone();
@@ -1046,13 +909,9 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1064,7 +923,7 @@ impl Resolve<ExecuteArgs> for DeleteImage {
"DeleteImage",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1073,11 +932,7 @@ impl Resolve<ExecuteArgs> for DeleteImage {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1112,7 +967,7 @@ impl Resolve<ExecuteArgs> for DeleteImage {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1126,7 +981,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
"PruneImages",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1134,11 +989,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1155,7 +1006,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_images = true)?;
let mut update = update.clone();
@@ -1177,13 +1028,9 @@ impl Resolve<ExecuteArgs> for PruneImages {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1195,7 +1042,7 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
"DeleteVolume",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1204,11 +1051,7 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1246,7 +1089,7 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1260,7 +1103,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
"PruneVolumes",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1268,11 +1111,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1289,7 +1128,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_volumes = true)?;
let mut update = update.clone();
@@ -1311,13 +1150,9 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1329,7 +1164,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
"PruneDockerBuilders",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1337,11 +1172,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1358,7 +1189,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_builders = true)?;
let mut update = update.clone();
@@ -1380,13 +1211,9 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1398,7 +1225,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
"PruneBuildx",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1406,11 +1233,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1427,7 +1250,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_buildx = true)?;
let mut update = update.clone();
@@ -1449,13 +1272,9 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1467,7 +1286,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
"PruneSystem",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1475,11 +1294,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
@@ -1496,7 +1311,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
// Will check to ensure server not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pruning_system = true)?;
let mut update = update.clone();
@@ -1517,13 +1332,9 @@ impl Resolve<ExecuteArgs> for PruneSystem {
};
update.logs.push(log);
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
+47 -120
View File
@@ -39,7 +39,7 @@ use crate::{
add_update_without_send, init_execution_update, update_update,
},
},
monitor::{refresh_server_cache, refresh_swarm_cache},
monitor::{update_cache_for_server, update_cache_for_swarm},
permission::get_check_permissions,
resource,
stack::{
@@ -69,23 +69,18 @@ impl Resolve<ExecuteArgs> for BatchDeployStack {
"BatchDeployStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeployStack>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchDeployStack>(&self.pattern, user)
.await?,
)
}
}
@@ -95,7 +90,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
"DeployStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -105,11 +100,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut stack, swarm_or_server) = setup_stack_execution(
&self.stack,
@@ -136,7 +127,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
// Will check to ensure stack not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.deploying = true)?;
let mut update = update.clone();
@@ -322,18 +313,14 @@ impl Resolve<ExecuteArgs> for DeployStack {
match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
SwarmOrServer::Server(server) => {
refresh_server_cache(&server, true).await;
update_cache_for_server(&server, true).await;
}
}
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -355,20 +342,18 @@ impl Resolve<ExecuteArgs> for BatchDeployStackIfChanged {
"BatchDeployStackIfChanged",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeployStackIfChanged>(
&self.pattern,
self.tags,
user,
)
.await?,
@@ -381,7 +366,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
"DeployStackIfChanged",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -390,11 +375,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let stack = get_check_permissions::<Stack>(
&self.stack,
@@ -452,7 +433,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
.resolve(&ExecuteArgs {
user: user.clone(),
update,
task_id: *task_id,
id: *id,
})
.await
}
@@ -591,7 +572,7 @@ async fn deploy_services(
.resolve(&ExecuteArgs {
user: user.clone(),
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
}
@@ -622,7 +603,7 @@ async fn restart_services(
.resolve(&ExecuteArgs {
user: user.clone(),
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
}
@@ -785,23 +766,18 @@ impl Resolve<ExecuteArgs> for BatchPullStack {
"BatchPullStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchPullStack>(
&self.pattern,
self.tags,
user,
)
.await?,
super::batch_execute::<BatchPullStack>(&self.pattern, user)
.await?,
)
}
}
@@ -902,7 +878,7 @@ pub async fn pull_stack_inner(
.await?;
// Ensure cached stack state up to date by updating server cache
refresh_server_cache(server, true).await;
update_cache_for_server(server, true).await;
Ok(res)
}
@@ -912,7 +888,7 @@ impl Resolve<ExecuteArgs> for PullStack {
"PullStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -921,11 +897,7 @@ impl Resolve<ExecuteArgs> for PullStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
@@ -959,7 +931,7 @@ impl Resolve<ExecuteArgs> for PullStack {
// Will check to ensure stack not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.pulling = true)?;
let mut update = update.clone();
@@ -976,10 +948,6 @@ impl Resolve<ExecuteArgs> for PullStack {
update.logs.extend(res.logs);
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -991,7 +959,7 @@ impl Resolve<ExecuteArgs> for StartStack {
"StartStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1000,11 +968,7 @@ impl Resolve<ExecuteArgs> for StartStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<StartStack>(
&self.stack,
@@ -1024,7 +988,7 @@ impl Resolve<ExecuteArgs> for RestartStack {
"RestartStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1033,11 +997,7 @@ impl Resolve<ExecuteArgs> for RestartStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<RestartStack>(
&self.stack,
@@ -1059,7 +1019,7 @@ impl Resolve<ExecuteArgs> for PauseStack {
"PauseStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1068,11 +1028,7 @@ impl Resolve<ExecuteArgs> for PauseStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<PauseStack>(
&self.stack,
@@ -1092,7 +1048,7 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
"UnpauseStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1101,11 +1057,7 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<UnpauseStack>(
&self.stack,
@@ -1125,7 +1077,7 @@ impl Resolve<ExecuteArgs> for StopStack {
"StopStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1134,11 +1086,7 @@ impl Resolve<ExecuteArgs> for StopStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<StopStack>(
&self.stack,
@@ -1170,23 +1118,18 @@ impl Resolve<ExecuteArgs> for BatchDestroyStack {
"BatchDestroyStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
)
)]
async fn resolve(
self,
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
super::batch_execute::<BatchDestroyStack>(
&self.pattern,
self.tags,
user,
)
.await
.map_err(Into::into)
super::batch_execute::<BatchDestroyStack>(&self.pattern, user)
.await
.map_err(Into::into)
}
}
@@ -1195,7 +1138,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
"DestroyStack",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1206,11 +1149,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
@@ -1239,7 +1178,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
// Will check to ensure stack not already busy before updating, and return Err if so.
// The returned guard will set the action state back to default when dropped.
let action_guard =
let _action_guard =
action_state.update(|state| state.destroying = true)?;
let mut update = update.clone();
@@ -1266,13 +1205,9 @@ impl Resolve<ExecuteArgs> for DestroyStack {
}
// Ensure cached stack state up to date by updating swarm cache
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1298,7 +1233,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
"RunStackService",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1308,11 +1243,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut stack, swarm_or_server) = setup_stack_execution(
&self.stack,
@@ -1343,7 +1274,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
let action_state =
action_states().stack.get_or_insert_default(&stack.id).await;
let action_guard =
let _action_guard =
action_state.update(|state| state.deploying = true)?;
let mut update = update.clone();
@@ -1403,10 +1334,6 @@ impl Resolve<ExecuteArgs> for RunStackService {
update.logs.push(log);
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
+33 -131
View File
@@ -1,6 +1,10 @@
use formatting::format_serror;
use komodo_client::{
api::execute::*,
api::execute::{
CreateSwarmConfig, CreateSwarmSecret, RemoveSwarmConfigs,
RemoveSwarmNodes, RemoveSwarmSecrets, RemoveSwarmServices,
RemoveSwarmStacks, RotateSwarmConfig, RotateSwarmSecret,
},
entities::{permission::PermissionLevel, swarm::Swarm},
};
use mogh_resolver::Resolve;
@@ -8,7 +12,7 @@ use mogh_resolver::Resolve;
use crate::{
api::execute::ExecuteArgs,
helpers::{swarm::swarm_request, update::update_update},
monitor::refresh_swarm_cache,
monitor::update_cache_for_swarm,
permission::get_check_permissions,
};
@@ -17,7 +21,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
"RemoveSwarmNodes",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -27,11 +31,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -55,7 +55,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Nodes",
@@ -72,78 +72,12 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
}
}
impl Resolve<ExecuteArgs> for UpdateSwarmNode {
#[instrument(
"UpdateSwarmNode",
skip_all,
fields(
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
node = self.node,
availability = format!("{:?}", self.availability),
label_add = format!("{:?}", self.label_add),
label_rm = format!("{:?}", self.label_rm),
role = format!("{:?}", self.role),
)
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
PermissionLevel::Execute.into(),
)
.await?;
update_update(update.clone()).await?;
let mut update = update.clone();
match swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::UpdateSwarmNode {
node: self.node,
availability: self.availability,
label_add: self.label_add,
label_rm: self.label_rm,
role: self.role,
},
)
.await
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Update Swarm Node",
format_serror(
&e.context("Failed to update swarm node").into(),
),
),
};
update.finalize();
update_update(update.clone()).await?;
Ok(update)
}
}
impl Resolve<ExecuteArgs> for RemoveSwarmStacks {
#[instrument(
"RemoveSwarmStacks",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -153,11 +87,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmStacks {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -181,7 +111,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmStacks {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Stacks",
@@ -203,7 +133,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
"RemoveSwarmServices",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -212,11 +142,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -239,7 +165,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Services",
@@ -261,7 +187,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
"CreateSwarmConfig",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -270,11 +196,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -300,7 +222,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Create Swarm Config",
@@ -322,7 +244,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
"RotateSwarmConfig",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -331,11 +253,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -359,7 +277,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
{
Ok(logs) => {
update.logs.extend(logs);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Rotate Swarm Config",
@@ -381,7 +299,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
"RemoveSwarmConfigs",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -390,11 +308,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -417,7 +331,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Configs",
@@ -439,7 +353,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
"CreateSwarmSecret",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -448,11 +362,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -479,7 +389,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Create Swarm Secret",
@@ -501,7 +411,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
"RotateSwarmSecret",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -510,11 +420,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -538,7 +444,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
{
Ok(logs) => {
update.logs.extend(logs);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Rotate Swarm Secret",
@@ -560,7 +466,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
"RemoveSwarmSecrets",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -569,11 +475,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -596,7 +498,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
{
Ok(log) => {
update.logs.push(log);
refresh_swarm_cache(&swarm, true).await;
update_cache_for_swarm(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Secrets",
+198 -106
View File
@@ -21,7 +21,6 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
swarm::Swarm,
sync::ResourceSync,
update::{Log, Update},
user::sync_user,
@@ -54,7 +53,7 @@ impl Resolve<ExecuteArgs> for RunSync {
"RunSync",
skip_all,
fields(
task_id = task_id.to_string(),
id = id.to_string(),
operator = user.id,
update_id = update.id,
sync = self.sync,
@@ -64,11 +63,7 @@ impl Resolve<ExecuteArgs> for RunSync {
)]
async fn resolve(
self,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let RunSync {
sync,
@@ -98,7 +93,7 @@ impl Resolve<ExecuteArgs> for RunSync {
// This will set action state back to default when dropped.
// Will also check to ensure sync not already busy before updating.
let action_guard =
let _action_guard =
action_state.update(|state| state.syncing = true)?;
let mut update = update.clone();
@@ -139,36 +134,56 @@ impl Resolve<ExecuteArgs> for RunSync {
let Some(resource_type) = match_resource_type else {
return Some(name_or_id);
};
macro_rules! resolve_id_to_name {
($(($Variant:ident, $field:ident)),* $(,)?) => {
match ObjectId::from_str(&name_or_id) {
Ok(_) => match resource_type {
$(
ResourceTargetVariant::$Variant => all_resources
.$field
.get(&name_or_id)
.map(|r| r.name.clone()),
)*
ResourceTargetVariant::System => None,
},
Err(_) => Some(name_or_id),
}
};
match ObjectId::from_str(&name_or_id) {
Ok(_) => match resource_type {
ResourceTargetVariant::Swarm => all_resources
.swarms
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Server => all_resources
.servers
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Stack => all_resources
.stacks
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Deployment => all_resources
.deployments
.get(&name_or_id)
.map(|d| d.name.clone()),
ResourceTargetVariant::Build => all_resources
.builds
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Repo => all_resources
.repos
.get(&name_or_id)
.map(|r| r.name.clone()),
ResourceTargetVariant::Procedure => all_resources
.procedures
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::Action => all_resources
.actions
.get(&name_or_id)
.map(|p| p.name.clone()),
ResourceTargetVariant::ResourceSync => all_resources
.syncs
.get(&name_or_id)
.map(|s| s.name.clone()),
ResourceTargetVariant::Builder => all_resources
.builders
.get(&name_or_id)
.map(|b| b.name.clone()),
ResourceTargetVariant::Alerter => all_resources
.alerters
.get(&name_or_id)
.map(|a| a.name.clone()),
ResourceTargetVariant::System => None,
},
Err(_) => Some(name_or_id),
}
// New resource types need to be added here manually.
resolve_id_to_name!(
(Server, servers),
(Swarm, swarms),
(Stack, stacks),
(Deployment, deployments),
(Build, builds),
(Repo, repos),
(Procedure, procedures),
(Action, actions),
(ResourceSync, syncs),
(Builder, builders),
(Alerter, alerters),
)
})
.collect::<Vec<_>>()
});
@@ -216,39 +231,136 @@ impl Resolve<ExecuteArgs> for RunSync {
let delete = sync.config.managed || sync.config.delete;
macro_rules! get_deltas {
($(($var:ident, $Type:ident, $field:ident)),* $(,)?) => {
$(
let $var = if sync.config.include_resources {
get_updates_for_execution::<$Type>(
resources.$field,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
)*
};
}
// New resource types need to be added here manually.
get_deltas!(
(server_deltas, Server, servers),
(swarm_deltas, Swarm, swarms),
(stack_deltas, Stack, stacks),
(deployment_deltas, Deployment, deployments),
(build_deltas, Build, builds),
(repo_deltas, Repo, repos),
(procedure_deltas, Procedure, procedures),
(action_deltas, Action, actions),
(builder_deltas, Builder, builders),
(alerter_deltas, Alerter, alerters),
(resource_sync_deltas, ResourceSync, resource_syncs),
);
let server_deltas = if sync.config.include_resources {
get_updates_for_execution::<Server>(
resources.servers,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let stack_deltas = if sync.config.include_resources {
get_updates_for_execution::<Stack>(
resources.stacks,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let deployment_deltas = if sync.config.include_resources {
get_updates_for_execution::<Deployment>(
resources.deployments,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let build_deltas = if sync.config.include_resources {
get_updates_for_execution::<Build>(
resources.builds,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let repo_deltas = if sync.config.include_resources {
get_updates_for_execution::<Repo>(
resources.repos,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let procedure_deltas = if sync.config.include_resources {
get_updates_for_execution::<Procedure>(
resources.procedures,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let action_deltas = if sync.config.include_resources {
get_updates_for_execution::<Action>(
resources.actions,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let builder_deltas = if sync.config.include_resources {
get_updates_for_execution::<Builder>(
resources.builders,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let alerter_deltas = if sync.config.include_resources {
get_updates_for_execution::<Alerter>(
resources.alerters,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let resource_sync_deltas = if sync.config.include_resources {
get_updates_for_execution::<entities::sync::ResourceSync>(
resources.resource_syncs,
delete,
match_resource_type,
match_resources.as_deref(),
&id_to_tags,
&sync.config.match_tags,
)
.await?
} else {
Default::default()
};
let (
variables_to_create,
@@ -283,11 +395,9 @@ impl Resolve<ExecuteArgs> for RunSync {
Default::default()
};
// New resource types need to be added here manually.
if deploy_cache.is_empty()
&& resource_sync_deltas.no_changes()
&& server_deltas.no_changes()
&& swarm_deltas.no_changes()
&& deployment_deltas.no_changes()
&& stack_deltas.no_changes()
&& build_deltas.no_changes()
@@ -311,19 +421,11 @@ impl Resolve<ExecuteArgs> for RunSync {
),
);
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
return Ok(update);
}
// =====================================================
// The ordering these are executed does matter, since
// latter resources may depend on prior synced resources
// already being updated with the declared state.
// =====================================================
// =================
// No deps
maybe_extend(
@@ -344,7 +446,10 @@ impl Resolve<ExecuteArgs> for RunSync {
)
.await,
);
maybe_extend(
&mut update.logs,
ResourceSync::execute_sync_updates(resource_sync_deltas).await,
);
maybe_extend(
&mut update.logs,
Server::execute_sync_updates(server_deltas).await,
@@ -358,43 +463,34 @@ impl Resolve<ExecuteArgs> for RunSync {
Action::execute_sync_updates(action_deltas).await,
);
// Depends on server
maybe_extend(
&mut update.logs,
Swarm::execute_sync_updates(swarm_deltas).await,
);
// Depends on server
// Dependent on server
maybe_extend(
&mut update.logs,
Builder::execute_sync_updates(builder_deltas).await,
);
// Depends on server / builder
maybe_extend(
&mut update.logs,
Repo::execute_sync_updates(repo_deltas).await,
);
// Depends on builder / repo
// Dependant on builder
maybe_extend(
&mut update.logs,
Build::execute_sync_updates(build_deltas).await,
);
// Depends on server / repo
maybe_extend(
&mut update.logs,
Stack::execute_sync_updates(stack_deltas).await,
);
// Depends on repo
maybe_extend(
&mut update.logs,
ResourceSync::execute_sync_updates(resource_sync_deltas).await,
);
// Depends on server / build
// Dependant on server / build
maybe_extend(
&mut update.logs,
Deployment::execute_sync_updates(deployment_deltas).await,
);
// Depends on everything
// stack only depends on server, but maybe will depend on build later.
maybe_extend(
&mut update.logs,
Stack::execute_sync_updates(stack_deltas).await,
);
// Dependant on everything
maybe_extend(
&mut update.logs,
Procedure::execute_sync_updates(procedure_deltas).await,
@@ -446,10 +542,6 @@ impl Resolve<ExecuteArgs> for RunSync {
}
update.finalize();
// Drop action guard before updating
// clients to requery action state
drop(action_guard);
update_update(update.clone()).await?;
Ok(update)
@@ -1,7 +1,7 @@
use anyhow::{Context, anyhow};
use axum::http::HeaderMap;
use hex::ToHex;
use hmac::{Hmac, KeyInit as _, Mac};
use hmac::{Hmac, Mac};
use serde::Deserialize;
use sha2::Sha256;
+4 -12
View File
@@ -1,5 +1,6 @@
use std::sync::Arc;
use anyhow::anyhow;
use axum::{Router, http::HeaderMap};
use komodo_client::entities::resource::Resource;
use mogh_cache::CloneCache;
@@ -40,21 +41,12 @@ trait VerifySecret {
/// Implemented on the integration struct, eg [integrations::github::Github]
trait ExtractBranch {
fn extract_branch(body: &str) -> anyhow::Result<String>;
/// Whether the webhook body's branch matches `expected`.
/// A mismatch is routine and only logged at debug; errors
/// only when the branch cannot be extracted from the body.
fn branch_matches(
body: &str,
expected: &str,
) -> anyhow::Result<bool> {
fn verify_branch(body: &str, expected: &str) -> anyhow::Result<()> {
let branch = Self::extract_branch(body)?;
if branch == expected {
Ok(true)
Ok(())
} else {
debug!(
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{expected}'"
);
Ok(false)
Err(anyhow!("request branch does not match expected"))
}
}
}
+32 -90
View File
@@ -1,4 +1,4 @@
use std::{str::FromStr, sync::OnceLock, time::Duration};
use std::{str::FromStr, sync::OnceLock};
use anyhow::{Context, anyhow};
use komodo_client::{
@@ -23,7 +23,6 @@ use crate::{
},
helpers::update::init_execution_update,
resource,
state::action_states,
};
use super::{ANY_BRANCH, ListenerLockCache};
@@ -38,6 +37,11 @@ impl super::CustomSecret for Build {
}
}
fn build_locks() -> &'static ListenerLockCache {
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
BUILD_LOCKS.get_or_init(Default::default)
}
pub async fn handle_build_webhook<B: super::ExtractBranch>(
build: Build,
body: String,
@@ -46,6 +50,12 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
return Ok(());
}
// Acquire and hold lock to make a task queue for
// subsequent listener calls on same resource.
// It would fail if we let it go through from action state busy.
let lock = build_locks().get_or_insert_default(&build.id).await;
let _lock = lock.lock().await;
// Use the correct target branch when using linked repo.
let branch = if build.config.linked_repo.is_empty() {
build.config.branch
@@ -57,82 +67,25 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
.branch
};
if !B::branch_matches(&body, &branch)? {
return Ok(());
}
// Cancel if currently building
if action_states()
.build
.get(&build.id)
.await
.and_then(|states| {
states.get().ok().map(|states| states.building)
})
.unwrap_or_default()
{
let user = git_webhook_user().to_owned();
let cancel = ExecuteRequest::CancelBuild(CancelBuild {
build: build.id.clone(),
});
let update = init_execution_update(&cancel, &user).await?;
let ExecuteRequest::CancelBuild(cancel) = cancel else {
unreachable!()
};
cancel
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
})
.await
.ok();
poll_build_until_cancelled(&build.id).await?;
}
B::verify_branch(&body, &branch)?;
let user = git_webhook_user().to_owned();
let run = ExecuteRequest::RunBuild(RunBuild { build: build.id });
let update = init_execution_update(&run, &user).await?;
let ExecuteRequest::RunBuild(run) = run else {
let req = ExecuteRequest::RunBuild(RunBuild { build: build.id });
let update = init_execution_update(&req, &user).await?;
let ExecuteRequest::RunBuild(req) = req else {
unreachable!()
};
run
req
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
Ok(())
}
async fn poll_build_until_cancelled(
build_id: &String,
) -> anyhow::Result<()> {
let action_states = action_states();
// Poll to ensure cancelled
for _ in 0..10 {
if !action_states
.build
.get(build_id)
.await
.and_then(|states| {
states.get().ok().map(|states| states.building)
})
.unwrap_or_default()
{
return Ok(());
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
Err(anyhow!("Build still running after cancel"))
}
// ======
// REPO
// ======
@@ -168,7 +121,7 @@ impl RepoExecution for CloneRepo {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -192,7 +145,7 @@ impl RepoExecution for PullRepo {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -216,7 +169,7 @@ impl RepoExecution for BuildRepo {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -267,9 +220,7 @@ async fn handle_repo_webhook_inner<
let lock = repo_locks().get_or_insert_default(&repo.id).await;
let _lock = lock.lock().await;
if !B::branch_matches(&body, &repo.config.branch)? {
return Ok(());
}
B::verify_branch(&body, &repo.config.branch)?;
E::resolve(repo).await
}
@@ -321,7 +272,7 @@ impl StackExecution for DeployStack {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -339,7 +290,7 @@ impl StackExecution for DeployStack {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -400,9 +351,7 @@ pub async fn handle_stack_webhook_inner<
.branch
};
if !B::branch_matches(&body, &branch)? {
return Ok(());
}
B::verify_branch(&body, &branch)?;
E::resolve(stack).await.map_err(|e| e.error)
}
@@ -454,7 +403,7 @@ impl SyncExecution for RunSync {
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -515,9 +464,7 @@ async fn handle_sync_webhook_inner<
.branch
};
if !B::branch_matches(&body, &branch)? {
return Ok(());
}
B::verify_branch(&body, &branch)?;
E::resolve(sync).await
}
@@ -554,10 +501,8 @@ pub async fn handle_procedure_webhook<B: super::ExtractBranch>(
procedure_locks().get_or_insert_default(&procedure.id).await;
let _lock = lock.lock().await;
if target_branch != ANY_BRANCH
&& !B::branch_matches(&body, target_branch)?
{
return Ok(());
if target_branch != ANY_BRANCH {
B::verify_branch(&body, target_branch)?;
}
let user = git_webhook_user().to_owned();
@@ -572,7 +517,7 @@ pub async fn handle_procedure_webhook<B: super::ExtractBranch>(
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -612,10 +557,7 @@ pub async fn handle_action_webhook<B: super::ExtractBranch>(
let branch = B::extract_branch(&body)?;
if target_branch != ANY_BRANCH && branch != target_branch {
debug!(
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{target_branch}'"
);
return Ok(());
return Err(anyhow!("request branch does not match expected"));
}
let user = git_webhook_user().to_owned();
@@ -641,7 +583,7 @@ pub async fn handle_action_webhook<B: super::ExtractBranch>(
.resolve(&ExecuteArgs {
user,
update,
task_id: Uuid::new_v4(),
id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
-23
View File
@@ -222,31 +222,8 @@ where
.status_code(StatusCode::BAD_REQUEST)?;
P::verify_secret(headers, body, R::custom_secret(&resource))
.status_code(StatusCode::UNAUTHORIZED)?;
info!(
resource_type = R::resource_type().to_string(),
resource_id = id,
source_ip = ip.to_string(),
"Successfully authenticated incoming webhook"
);
debug!(
resource_type = R::resource_type().to_string(),
resource_id = id,
source_ip = ip.to_string(),
"Webhook body: {body}"
);
mogh_error::Result::Ok(resource)
}
.with_failure_rate_limit_using_ip(&GENERAL_RATE_LIMITER, &ip)
.await
.inspect_err(|e| {
warn!(
resource_id = id,
source_ip = ip.to_string(),
"Incoming webhook failed | ERROR: {:#}",
e.error
);
})
}
+12 -61
View File
@@ -3,8 +3,7 @@ use komodo_client::{
api::read::*,
entities::{
action::{
Action, ActionActionState, ActionListItem, ActionSortBy,
ActionState,
Action, ActionActionState, ActionListItem, ActionState,
},
permission::PermissionLevel,
},
@@ -12,13 +11,13 @@ use komodo_client::{
use mogh_resolver::Resolve;
use crate::{
helpers::query::{get_action_state, get_all_tags},
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_state_cache, action_states},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetAction {
async fn resolve(
@@ -46,45 +45,15 @@ impl Resolve<ReadArgs> for ListActions {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<ActionListItem> =
match self.sort_by {
ActionSortBy::Name => resource::ListItemSort::Name,
ActionSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
ActionSortBy::NextRun => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.next_scheduled_run
.cmp(&b.info.next_scheduled_run)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let actions = resource::list_items_for_user::<Action>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|action| {
states.is_empty() || states.contains(&action.info.state)
},
Ok(
resource::list_for_user::<Action>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
.await?;
Ok(actions)
}
}
@@ -98,28 +67,12 @@ impl Resolve<ReadArgs> for ListFullActions {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Action, _>(
resource::list_full_for_user::<Action>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|action| {
let states = states.clone();
async move {
if states.is_empty()
|| states.contains(&get_action_state(&action.id).await)
{
Some(action)
} else {
None
}
}
},
)
.await?,
)
@@ -154,8 +107,6 @@ impl Resolve<ReadArgs> for GetActionsSummary {
) -> mogh_error::Result<GetActionsSummaryResponse> {
let actions = resource::list_full_for_user::<Action>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+1 -1
View File
@@ -40,7 +40,7 @@ impl Resolve<ReadArgs> for ListAlerts {
FindOptions::builder()
.sort(doc! { "ts": -1 })
.limit(NUM_ALERTS_PER_PAGE as i64)
.skip(self.page.saturating_mul(NUM_ALERTS_PER_PAGE))
.skip(self.page * NUM_ALERTS_PER_PAGE)
.build(),
)
.await
+3 -26
View File
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
use komodo_client::{
api::read::*,
entities::{
alerter::{Alerter, AlerterListItem, AlerterSortBy},
alerter::{Alerter, AlerterListItem},
permission::PermissionLevel,
},
};
@@ -17,7 +17,7 @@ use crate::{
state::db_client,
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetAlerter {
async fn resolve(
@@ -45,30 +45,12 @@ impl Resolve<ReadArgs> for ListAlerters {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<AlerterListItem> =
match self.sort_by {
AlerterSortBy::Name => resource::ListItemSort::Name,
AlerterSortBy::Type => {
resource::ListItemSort::DbField("config.endpoint.type")
}
AlerterSortBy::Enabled => {
resource::ListItemSort::DbField("config.enabled")
}
};
Ok(
resource::list_items_for_user::<Alerter>(
resource::list_for_user::<Alerter>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|_| true,
)
.await?,
)
@@ -85,12 +67,9 @@ impl Resolve<ReadArgs> for ListFullAlerters {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user::<Alerter>(
self.query,
limit as i64,
self.page.saturating_mul(limit),
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -106,8 +85,6 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetAlertersSummaryResponse> {
let query = match list_resource_ids_for_user::<Alerter>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
+12 -68
View File
@@ -11,9 +11,7 @@ use komodo_client::{
api::read::*,
entities::{
Operation,
build::{
Build, BuildActionState, BuildListItem, BuildSortBy, BuildState,
},
build::{Build, BuildActionState, BuildListItem, BuildState},
permission::PermissionLevel,
update::UpdateStatus,
},
@@ -27,7 +25,7 @@ use crate::{
state::{action_states, build_state_cache, db_client},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetBuild {
async fn resolve(
@@ -55,47 +53,15 @@ impl Resolve<ReadArgs> for ListBuilds {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<BuildListItem> =
match self.sort_by {
BuildSortBy::Name => resource::ListItemSort::Name,
BuildSortBy::Source => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.files_on_host
.cmp(&b.info.files_on_host)
.then_with(|| {
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
})
.then_with(|| a.info.repo.cmp(&b.info.repo))
.then_with(|| a.name.cmp(&b.name))
}))
}
BuildSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let builds = resource::list_items_for_user::<Build>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|build| states.is_empty() || states.contains(&build.info.state),
Ok(
resource::list_for_user::<Build>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
.await?;
Ok(builds)
}
}
@@ -109,29 +75,12 @@ impl Resolve<ReadArgs> for ListFullBuilds {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Build, _>(
resource::list_full_for_user::<Build>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|build| {
let states = states.clone();
async move {
if states.is_empty()
|| states
.contains(&resource::get_build_state(&build.id).await)
{
Some(build)
} else {
None
}
}
},
)
.await?,
)
@@ -166,8 +115,6 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
) -> mogh_error::Result<GetBuildsSummaryResponse> {
let builds = resource::list_full_for_user::<Build>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
@@ -217,8 +164,7 @@ impl Resolve<ReadArgs> for GetBuildMonthlyStats {
let curr_ts = unix_timestamp_ms() as i64;
let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS;
let close_ts =
next_day - (self.page as i64).saturating_mul(30 * ONE_DAY_MS);
let close_ts = next_day - self.page as i64 * 30 * ONE_DAY_MS;
let open_ts = close_ts - 30 * ONE_DAY_MS;
let mut build_updates = db_client()
@@ -335,8 +281,6 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
};
let builds = resource::list_full_for_user::<Build>(
self.query,
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
+3 -31
View File
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
use komodo_client::{
api::read::*,
entities::{
builder::{Builder, BuilderListItem, BuilderSortBy},
builder::{Builder, BuilderListItem},
permission::PermissionLevel,
},
};
@@ -17,7 +17,7 @@ use crate::{
state::db_client,
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetBuilder {
async fn resolve(
@@ -45,35 +45,12 @@ impl Resolve<ReadArgs> for ListBuilders {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<BuilderListItem> =
match self.sort_by {
BuilderSortBy::Name => resource::ListItemSort::Name,
BuilderSortBy::Provider => {
resource::ListItemSort::DbField("config.type")
}
BuilderSortBy::InstanceType => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.instance_type
.cmp(&b.info.instance_type)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
Ok(
resource::list_items_for_user::<Builder>(
resource::list_for_user::<Builder>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|_| true,
)
.await?,
)
@@ -90,12 +67,9 @@ impl Resolve<ReadArgs> for ListFullBuilders {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user::<Builder>(
self.query,
limit as i64,
self.page.saturating_mul(limit),
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -111,8 +85,6 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetBuildersSummaryResponse> {
let query = match list_resource_ids_for_user::<Builder>(
None,
None,
None,
user,
PermissionLevel::Read.into(),
+29 -99
View File
@@ -6,8 +6,8 @@ use komodo_client::{
entities::{
SwarmOrServer,
deployment::{
Deployment, DeploymentActionState, DeploymentListItem,
DeploymentSortBy, DeploymentState,
Deployment, DeploymentActionState, DeploymentConfig,
DeploymentListItem, DeploymentState,
},
docker::{
container::{Container, ContainerStats},
@@ -25,9 +25,7 @@ use reqwest::StatusCode;
use crate::{
helpers::{
periphery_client,
query::{get_all_tags, get_deployment_state},
swarm::swarm_request,
periphery_client, query::get_all_tags, swarm::swarm_request,
},
permission::get_check_permissions,
resource::{self, setup_deployment_execution},
@@ -36,7 +34,7 @@ use crate::{
},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetDeployment {
async fn resolve(
@@ -65,66 +63,21 @@ impl Resolve<ReadArgs> for ListDeployments {
get_all_tags(None).await?
};
let only_update_available = self.query.specific.update_available;
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<DeploymentListItem> =
match self.sort_by {
DeploymentSortBy::Name => resource::ListItemSort::Name,
DeploymentSortBy::Image => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.image
.cmp(&b.info.image)
.then_with(|| a.name.cmp(&b.name))
}))
}
DeploymentSortBy::Host => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
let host_a = if a.info.swarm_id.is_empty() {
&a.info.server_name
} else {
&a.info.swarm_name
};
let host_b = if b.info.swarm_id.is_empty() {
&b.info.server_name
} else {
&b.info.swarm_name
};
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
}))
}
DeploymentSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| {
// Use ! with update available to order 'true' first
(!a.info.update_available)
.cmp(&!b.info.update_available)
})
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let deployments = resource::list_items_for_user::<Deployment>(
let deployments = resource::list_for_user::<Deployment>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|deployment| {
(!only_update_available || deployment.info.update_available)
&& (states.is_empty()
|| states.contains(&deployment.info.state))
},
)
.await?;
let deployments = if only_update_available {
deployments
.into_iter()
.filter(|deployment| deployment.info.update_available)
.collect()
} else {
deployments
};
Ok(deployments)
}
}
@@ -139,32 +92,12 @@ impl Resolve<ReadArgs> for ListFullDeployments {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Deployment, _>(
resource::list_full_for_user::<Deployment>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|deployment| {
let states = states.clone();
async move {
if states.is_empty()
|| states.contains(
&get_deployment_state(&deployment.id)
.await
.unwrap_or_default(),
)
{
Some(deployment)
} else {
None
}
}
},
)
.await?,
)
@@ -221,7 +154,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
SwarmOrServer::Swarm(swarm) => swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLog {
service: deployment.deployed_name().to_string(),
service: deployment.name,
tail,
timestamps,
no_task_ids: false,
@@ -234,7 +167,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
SwarmOrServer::Server(server) => periphery_client(&server)
.await?
.request(api::container::GetContainerLog {
name: deployment.deployed_name().to_string(),
name: deployment.name,
tail: cmp::min(tail, MAX_LOG_LENGTH),
timestamps,
})
@@ -273,7 +206,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
SwarmOrServer::Swarm(swarm) => swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLogSearch {
service: deployment.deployed_name().to_string(),
service: deployment.name,
terms,
combinator,
invert,
@@ -288,7 +221,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
SwarmOrServer::Server(server) => periphery_client(&server)
.await?
.request(api::container::GetContainerLogSearch {
name: deployment.deployed_name().to_string(),
name: deployment.name,
terms,
combinator,
invert,
@@ -341,7 +274,7 @@ impl Resolve<ReadArgs> for InspectDeploymentContainer {
periphery_client(&server)
.await?
.request(InspectContainer {
name: deployment.deployed_name().to_string(),
name: deployment.name,
})
.await
.context("Failed to inspect container on server")
@@ -374,7 +307,7 @@ impl Resolve<ReadArgs> for InspectDeploymentSwarmService {
swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::InspectSwarmService {
service: deployment.deployed_name().to_string(),
service: deployment.name,
},
)
.await
@@ -388,24 +321,25 @@ impl Resolve<ReadArgs> for GetDeploymentStats {
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ContainerStats> {
let deployment = get_check_permissions::<Deployment>(
let Deployment {
name,
config: DeploymentConfig { server_id, .. },
..
} = get_check_permissions::<Deployment>(
&self.deployment,
user,
PermissionLevel::Read.into(),
)
.await?;
if deployment.config.server_id.is_empty() {
if server_id.is_empty() {
return Err(
anyhow!("Deployment has no Server attached").into(),
anyhow!("deployment has no server attached").into(),
);
}
let server =
resource::get::<Server>(&deployment.config.server_id).await?;
let server = resource::get::<Server>(&server_id).await?;
let res = periphery_client(&server)
.await?
.request(api::container::GetContainerStats {
name: deployment.deployed_name().to_string(),
})
.request(api::container::GetContainerStats { name })
.await
.context("failed to get stats from periphery")?;
Ok(res)
@@ -440,8 +374,6 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
) -> mogh_error::Result<GetDeploymentsSummaryResponse> {
let deployments = resource::list_full_for_user::<Deployment>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
@@ -490,8 +422,6 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
};
let deployments = resource::list_full_for_user::<Deployment>(
self.query,
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
-590
View File
@@ -1,590 +0,0 @@
use std::cmp;
use anyhow::{Context as _, anyhow};
use database::bson::doc;
use komodo_client::{
api::read::*,
entities::{
ResourceTarget,
deployment::Deployment,
docker::{
container::{
Container, ContainerListItem, ContainerSortBy,
ContainerStateStatusEnum,
},
image::{Image, ImageHistoryResponseItem},
network::Network,
volume::Volume,
},
permission::PermissionLevel,
server::{Server, ServerQuery, ServerState},
stack::{Stack, StackServiceNames},
update::Log,
},
};
use mogh_resolver::Resolve;
use periphery_client::api as periphery;
use crate::{
api::read::{ReadArgs, list_limit},
helpers::{periphery_client, query::get_all_tags},
permission::{get_check_permissions, list_resources_for_user},
resource,
stack::compose_container_match_regex,
state::{db_client, server_status_cache},
};
impl Resolve<ReadArgs> for GetContainersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetContainersSummaryResponse> {
let servers = resource::list_full_for_user::<Server>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
)
.await
.context("failed to get servers from db")?;
let mut res = GetContainersSummaryResponse::default();
for server in servers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
for container in &docker.containers {
res.total += 1;
match container.state {
ContainerStateStatusEnum::Created
| ContainerStateStatusEnum::Paused
| ContainerStateStatusEnum::Exited => res.stopped += 1,
ContainerStateStatusEnum::Running => res.running += 1,
ContainerStateStatusEnum::Empty => res.unknown += 1,
_ => res.unhealthy += 1,
}
}
}
}
Ok(res)
}
}
impl Resolve<ReadArgs> for ListAllContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListAllContainersResponse> {
let all_tags = if self.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let servers = resource::list_for_user::<Server>(
ServerQuery::builder()
.names(self.servers.clone())
.tags(self.tags)
.build(),
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?;
let mut containers = Vec::<ContainerListItem>::new();
let limit = list_limit(self.limit);
// Match terms case insensitively.
let terms = self
.terms
.iter()
.map(|term| term.to_lowercase())
.collect::<Vec<_>>();
for server in servers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
let Some(docker) = &cache.docker else {
continue;
};
containers.extend(
docker
.containers
.iter()
.filter(|container| {
// Apply state filter if defined.
(self.state.is_empty() || self.state.contains(&container.state)) &&
// Apply terms filter if defined
(terms.is_empty()
// Match when all terms contained within a name.
|| {
let name = container.name.to_lowercase();
terms.iter().all(|term| name.contains(term))
})
})
.cloned(),
);
}
// The containers all come from the in memory status cache,
// so all matching containers are collected and sorted
// before applying pagination.
let compare = |a: &ContainerListItem, b: &ContainerListItem| {
match self.sort_by {
ContainerSortBy::Name => a.name.cmp(&b.name),
ContainerSortBy::Server => a.server_name.cmp(&b.server_name),
ContainerSortBy::State => a.state.cmp(&b.state),
ContainerSortBy::Image => a.image.cmp(&b.image),
ContainerSortBy::Networks => {
a.networks.first().cmp(&b.networks.first())
}
ContainerSortBy::Ports => a
.ports
.first()
.map(|port| port.private_port)
.cmp(&b.ports.first().map(|port| port.private_port)),
ContainerSortBy::Volumes => {
a.volumes.first().cmp(&b.volumes.first())
}
}
// Fall back to name based sorting for equal sort keys.
// Inside `compare`, so descending sorts are fully descending,
// matching the List<Resource> apis.
.then_with(|| a.name.cmp(&b.name))
};
if self.sort_desc {
containers.sort_by(|a, b| compare(b, a));
} else {
containers.sort_by(|a, b| compare(a, b));
}
let skip = limit.saturating_mul(self.page) as usize;
let take = if limit == 0 {
usize::MAX
} else {
limit as usize
};
Ok(containers.into_iter().skip(skip).take(take).collect())
}
}
impl Resolve<ReadArgs> for ListContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListContainersResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.containers.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Container> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.inspect(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect container: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(periphery::container::InspectContainer {
name: self.container,
})
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
// First check deployments with a matching deployed / custom container name.
// The empty check is required to avoid matching
// deployments with no custom name configured.
if !self.container.is_empty()
&& let Ok(Some(deployment)) = db_client()
.deployments
.find_one(doc! {
"$or": [
{ "info.deployed_name": &self.container },
{ "config.custom_name": &self.container },
]
})
.await
{
return Ok(GetResourceMatchingContainerResponse {
resource: ResourceTarget::Deployment(deployment.id).into(),
});
}
// Then check deployments matching by name
if let Ok(deployment) =
resource::get::<Deployment>(&self.container).await
&& deployment.custom_name() == self.container
{
return Ok(GetResourceMatchingContainerResponse {
resource: ResourceTarget::Deployment(deployment.id).into(),
});
}
// then check stacks
let stacks = list_resources_for_user::<Stack>(
doc! { "config.server_id": &server.id },
None,
None,
user,
PermissionLevel::Read.into(),
)
.await?;
// check matching stack
for stack in stacks {
for StackServiceNames {
service_name,
container_name,
..
} in stack
.info
.deployed_services
.unwrap_or(stack.info.latest_services)
{
let is_match = match compose_container_match_regex(&container_name)
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
{
Ok(regex) => regex,
Err(e) => {
warn!("{e:#}");
continue;
}
}.is_match(&self.container);
if is_match {
return Ok(GetResourceMatchingContainerResponse {
resource: ResourceTarget::Stack(stack.id).into(),
});
}
}
}
Ok(GetResourceMatchingContainerResponse { resource: None })
}
}
const MAX_LOG_LENGTH: u64 = 5000;
impl Resolve<ReadArgs> for GetContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Log> {
let GetContainerLog {
server,
container,
tail,
timestamps,
} = self;
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)
.await?
.request(periphery::container::GetContainerLog {
name: container,
tail: cmp::min(tail, MAX_LOG_LENGTH),
timestamps,
})
.await
.context("failed at call to periphery")?;
Ok(res)
}
}
impl Resolve<ReadArgs> for SearchContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Log> {
let SearchContainerLog {
server,
container,
terms,
combinator,
invert,
timestamps,
} = self;
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)
.await?
.request(periphery::container::GetContainerLogSearch {
name: container,
terms,
combinator,
invert,
timestamps,
})
.await
.context("failed at call to periphery")?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListComposeProjects {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListComposeProjectsResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.projects.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for ListNetworks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListNetworksResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.networks.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectNetwork {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Network> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect network: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(periphery::docker::InspectNetwork {
name: self.network,
})
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListImages {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListImagesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.images.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectImage {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Image> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!("Cannot inspect image: server is {:?}", cache.state)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(periphery::docker::InspectImage { name: self.image })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListImageHistory {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot get image history: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(periphery::docker::ImageHistory { name: self.image })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListVolumes {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListVolumesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.volumes.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectVolume {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Volume> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(periphery::docker::InspectVolume { name: self.volume })
.await?;
Ok(res)
}
}
+47 -92
View File
@@ -10,7 +10,7 @@ use komodo_client::{
ResourceTarget,
build::Build,
builder::{Builder, BuilderConfig},
config::{GitProvider, ImageRegistry},
config::{DockerRegistry, GitProvider},
permission::PermissionLevel,
repo::Repo,
server::Server,
@@ -44,7 +44,6 @@ mod alerter;
mod build;
mod builder;
mod deployment;
mod docker;
mod onboarding_key;
mod permission;
mod procedure;
@@ -67,17 +66,11 @@ pub struct ReadArgs {
pub user: User,
}
/// Resolve the page limit for List apis, falling back to the
/// configured `default_pagination_limit` when not provided.
fn list_limit(limit: Option<u64>) -> u64 {
limit.unwrap_or(core_config().default_pagination_limit)
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[strum_discriminants(name(ReadRequestMethod), derive(Display))]
#[strum_discriminants(name(ReadRequestVariant), derive(Display))]
#[args(ReadArgs)]
#[response(Response)]
#[error(mogh_error::Error)]
@@ -87,8 +80,7 @@ enum ReadRequest {
GetCoreInfo(GetCoreInfo),
ListSecrets(ListSecrets),
ListGitProvidersFromConfig(ListGitProvidersFromConfig),
#[serde(alias = "ListDockerRegistriesFromConfig")]
ListImageRegistriesFromConfig(ListImageRegistriesFromConfig),
ListDockerRegistriesFromConfig(ListDockerRegistriesFromConfig),
// ==== SWARM ====
GetSwarmsSummary(GetSwarmsSummary),
@@ -125,33 +117,22 @@ enum ReadRequest {
// ==== TERMINAL ====
ListTerminals(ListTerminals),
// ==== CONTAINER ====
#[serde(alias = "GetDockerContainersSummary")]
GetContainersSummary(GetContainersSummary),
#[serde(alias = "ListAllDockerContainers")]
ListAllContainers(ListAllContainers),
#[serde(alias = "ListDockerContainers")]
ListContainers(ListContainers),
#[serde(alias = "InspectDockerContainer")]
InspectContainer(InspectContainer),
// ==== DOCKER ====
GetDockerContainersSummary(GetDockerContainersSummary),
ListAllDockerContainers(ListAllDockerContainers),
ListDockerContainers(ListDockerContainers),
InspectDockerContainer(InspectDockerContainer),
GetResourceMatchingContainer(GetResourceMatchingContainer),
GetContainerLog(GetContainerLog),
SearchContainerLog(SearchContainerLog),
ListComposeProjects(ListComposeProjects),
#[serde(alias = "ListDockerNetworks")]
ListNetworks(ListNetworks),
#[serde(alias = "InspectDockerNetwork")]
InspectNetwork(InspectNetwork),
#[serde(alias = "ListDockerImages")]
ListImages(ListImages),
#[serde(alias = "InspectDockerImage")]
InspectImage(InspectImage),
#[serde(alias = "ListDockerImageHistory")]
ListImageHistory(ListImageHistory),
#[serde(alias = "ListDockerVolumes")]
ListVolumes(ListVolumes),
#[serde(alias = "InspectDockerVolume")]
InspectVolume(InspectVolume),
ListDockerNetworks(ListDockerNetworks),
InspectDockerNetwork(InspectDockerNetwork),
ListDockerImages(ListDockerImages),
InspectDockerImage(InspectDockerImage),
ListDockerImageHistory(ListDockerImageHistory),
ListDockerVolumes(ListDockerVolumes),
InspectDockerVolume(InspectDockerVolume),
// ==== SERVER STATS ====
GetSystemInformation(GetSystemInformation),
@@ -170,7 +151,6 @@ enum ReadRequest {
ListStacks(ListStacks),
ListFullStacks(ListFullStacks),
ListStackServices(ListStackServices),
ListAllStackServices(ListAllStackServices),
ListCommonStackExtraArgs(ListCommonStackExtraArgs),
ListCommonStackBuildExtraArgs(ListCommonStackBuildExtraArgs),
@@ -278,10 +258,8 @@ enum ReadRequest {
// ==== PROVIDER ====
GetGitProviderAccount(GetGitProviderAccount),
ListGitProviderAccounts(ListGitProviderAccounts),
#[serde(alias = "GetDockerRegistryAccount")]
GetImageRegistryAccount(GetImageRegistryAccount),
#[serde(alias = "ListDockerRegistryAccounts")]
ListImageRegistryAccounts(ListImageRegistryAccounts),
GetDockerRegistryAccount(GetDockerRegistryAccount),
ListDockerRegistryAccounts(ListDockerRegistryAccounts),
// ==== ONBOARDING KEY ====
ListOnboardingKeys(ListOnboardingKeys),
@@ -313,28 +291,18 @@ async fn handler(
Json(request): Json<ReadRequest>,
) -> mogh_error::Result<axum::response::Response> {
let req_id = Uuid::new_v4();
let method: ReadRequestMethod = (&request).into();
let variant: ReadRequestVariant = (&request).into();
let user_id = user.id.clone();
let username = user.username.clone();
trace!(
req_id = req_id.to_string(),
method = method.to_string(),
user_id,
username,
"READ REQUEST",
debug!(
"READ REQUEST {req_id} | METHOD: {variant} | USER: {} ({})",
user.username, user.id
);
let res = request.resolve(&ReadArgs { user }).await;
if let Err(e) = &res {
trace!(
req_id = req_id.to_string(),
method = method.to_string(),
user_id,
username,
"READ REQUEST | ERROR: {:#}",
debug!(
"READ REQUEST {req_id} | METHOD: {variant} | ERROR: {:#}",
e.error
);
}
@@ -376,7 +344,6 @@ impl Resolve<ReadArgs> for GetCoreInfo {
disable_websocket_reconnect: config.disable_websocket_reconnect,
enable_fancy_toml: config.enable_fancy_toml,
timezone: config.timezone.clone(),
default_pagination_limit: config.default_pagination_limit,
public_key: core_keys().load().public.to_string(),
};
Ok(info)
@@ -402,9 +369,7 @@ impl Resolve<ReadArgs> for ListSecrets {
ResourceTarget::Builder(id) => {
match resource::get::<Builder>(&id).await?.config {
BuilderConfig::Url(_) => None,
BuilderConfig::Server(config) => {
config.server_ids.first().cloned()
}
BuilderConfig::Server(config) => Some(config.server_id),
BuilderConfig::Aws(config) => {
secrets.extend(config.secrets);
None
@@ -459,13 +424,11 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
match resource::get::<Builder>(&id).await?.config {
BuilderConfig::Url(_) => {}
BuilderConfig::Server(config) => {
if let Some(server_id) = config.server_ids.first() {
merge_git_providers_for_server(
&mut providers,
server_id,
)
.await?;
}
merge_git_providers_for_server(
&mut providers,
&config.server_id,
)
.await?;
}
BuilderConfig::Aws(config) => {
merge_git_providers(
@@ -487,24 +450,18 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
let (builds, repos, syncs) = tokio::try_join!(
resource::list_full_for_user::<Build>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[]
),
resource::list_full_for_user::<Repo>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[]
),
resource::list_full_for_user::<ResourceSync>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[]
@@ -556,35 +513,33 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
//
impl Resolve<ReadArgs> for ListImageRegistriesFromConfig {
impl Resolve<ReadArgs> for ListDockerRegistriesFromConfig {
async fn resolve(
self,
_: &ReadArgs,
) -> mogh_error::Result<ListImageRegistriesFromConfigResponse> {
let mut registries = core_config().image_registries.clone();
) -> mogh_error::Result<ListDockerRegistriesFromConfigResponse> {
let mut registries = core_config().docker_registries.clone();
if let Some(target) = self.target {
match target {
ResourceTarget::Server(id) => {
merge_image_registries_for_server(&mut registries, &id)
merge_docker_registries_for_server(&mut registries, &id)
.await?;
}
ResourceTarget::Builder(id) => {
match resource::get::<Builder>(&id).await?.config {
BuilderConfig::Url(_) => {}
BuilderConfig::Server(config) => {
if let Some(server_id) = config.server_ids.first() {
merge_image_registries_for_server(
&mut registries,
server_id,
)
.await?;
}
merge_docker_registries_for_server(
&mut registries,
&config.server_id,
)
.await?;
}
BuilderConfig::Aws(config) => {
merge_image_registries(
merge_docker_registries(
&mut registries,
config.image_registries,
config.docker_registries,
);
}
}
@@ -642,14 +597,14 @@ fn merge_git_providers(
}
}
async fn merge_image_registries_for_server(
registries: &mut Vec<ImageRegistry>,
async fn merge_docker_registries_for_server(
registries: &mut Vec<DockerRegistry>,
server_id: &str,
) -> mogh_error::Result<()> {
let server = resource::get::<Server>(server_id).await?;
let more = periphery_client(&server)
.await?
.request(periphery_client::api::ListImageRegistries {})
.request(periphery_client::api::ListDockerRegistries {})
.await
.with_context(|| {
format!(
@@ -657,13 +612,13 @@ async fn merge_image_registries_for_server(
server.name
)
})?;
merge_image_registries(registries, more);
merge_docker_registries(registries, more);
Ok(())
}
fn merge_image_registries(
registries: &mut Vec<ImageRegistry>,
more: Vec<ImageRegistry>,
fn merge_docker_registries(
registries: &mut Vec<DockerRegistry>,
more: Vec<DockerRegistry>,
) {
for incoming_registry in more {
if let Some(registry) = registries
+12 -63
View File
@@ -3,21 +3,19 @@ use komodo_client::{
api::read::*,
entities::{
permission::PermissionLevel,
procedure::{
Procedure, ProcedureListItem, ProcedureSortBy, ProcedureState,
},
procedure::{Procedure, ProcedureState},
},
};
use mogh_resolver::Resolve;
use crate::{
helpers::query::{get_all_tags, get_procedure_state},
helpers::query::get_all_tags,
permission::get_check_permissions,
resource,
state::{action_states, procedure_state_cache},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetProcedure {
async fn resolve(
@@ -45,45 +43,15 @@ impl Resolve<ReadArgs> for ListProcedures {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<ProcedureListItem> =
match self.sort_by {
ProcedureSortBy::Name => resource::ListItemSort::Name,
ProcedureSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
ProcedureSortBy::NextRun => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.next_scheduled_run
.cmp(&b.info.next_scheduled_run)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let procedures = resource::list_items_for_user::<Procedure>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|procedure| {
states.is_empty() || states.contains(&procedure.info.state)
},
Ok(
resource::list_for_user::<Procedure>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
.await?;
Ok(procedures)
}
}
@@ -97,29 +65,12 @@ impl Resolve<ReadArgs> for ListFullProcedures {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Procedure, _>(
resource::list_full_for_user::<Procedure>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|procedure| {
let states = states.clone();
async move {
if states.is_empty()
|| states
.contains(&get_procedure_state(&procedure.id).await)
{
Some(procedure)
} else {
None
}
}
},
)
.await?,
)
@@ -133,8 +84,6 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
) -> mogh_error::Result<GetProceduresSummaryResponse> {
let procedures = resource::list_full_for_user::<Procedure>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+4 -4
View File
@@ -61,11 +61,11 @@ impl Resolve<ReadArgs> for ListGitProviderAccounts {
}
}
impl Resolve<ReadArgs> for GetImageRegistryAccount {
impl Resolve<ReadArgs> for GetDockerRegistryAccount {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetImageRegistryAccountResponse> {
) -> mogh_error::Result<GetDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read docker registry accounts")
@@ -83,11 +83,11 @@ impl Resolve<ReadArgs> for GetImageRegistryAccount {
}
}
impl Resolve<ReadArgs> for ListImageRegistryAccounts {
impl Resolve<ReadArgs> for ListDockerRegistryAccounts {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListImageRegistryAccountsResponse> {
) -> mogh_error::Result<ListDockerRegistryAccountsResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read docker registry accounts")
+11 -58
View File
@@ -3,9 +3,7 @@ use komodo_client::{
api::read::*,
entities::{
permission::PermissionLevel,
repo::{
Repo, RepoActionState, RepoListItem, RepoSortBy, RepoState,
},
repo::{Repo, RepoActionState, RepoListItem, RepoState},
},
};
use mogh_resolver::Resolve;
@@ -17,7 +15,7 @@ use crate::{
state::{action_states, repo_state_cache},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetRepo {
async fn resolve(
@@ -45,41 +43,15 @@ impl Resolve<ReadArgs> for ListRepos {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<RepoListItem> =
match self.sort_by {
RepoSortBy::Name => resource::ListItemSort::Name,
RepoSortBy::Repo => {
resource::ListItemSort::DbField("config.repo")
}
RepoSortBy::Branch => {
resource::ListItemSort::DbField("config.branch")
}
RepoSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let repos = resource::list_items_for_user::<Repo>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|repo| states.is_empty() || states.contains(&repo.info.state),
Ok(
resource::list_for_user::<Repo>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
.await?;
Ok(repos)
}
}
@@ -93,29 +65,12 @@ impl Resolve<ReadArgs> for ListFullRepos {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Repo, _>(
resource::list_full_for_user::<Repo>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|repo| {
let states = states.clone();
async move {
if states.is_empty()
|| states
.contains(&resource::get_repo_state(&repo.id).await)
{
Some(repo)
} else {
None
}
}
},
)
.await?,
)
@@ -150,8 +105,6 @@ impl Resolve<ReadArgs> for GetReposSummary {
) -> mogh_error::Result<GetReposSummaryResponse> {
let repos = resource::list_full_for_user::<Repo>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+15 -68
View File
@@ -1,15 +1,13 @@
use std::cmp::Ordering;
use futures_util::future::join_all;
use komodo_client::{
api::read::*,
entities::{
ResourceTarget,
action::{Action, ActionQuerySpecifics},
action::Action,
permission::PermissionLevel,
procedure::{Procedure, ProcedureQuerySpecifics},
procedure::Procedure,
resource::{ResourceQuery, TemplatesQueryBehavior},
schedule::{Schedule, ScheduleSortBy},
schedule::Schedule,
},
};
use mogh_resolver::Resolve;
@@ -20,7 +18,7 @@ use crate::{
schedule::get_schedule_item_info,
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for ListSchedules {
async fn resolve(
@@ -31,36 +29,24 @@ impl Resolve<ReadArgs> for ListSchedules {
let (actions, procedures) = tokio::try_join!(
list_full_for_user::<Action>(
ResourceQuery {
names: Default::default(),
templates: TemplatesQueryBehavior::Include,
tag_behavior: self.tag_behavior,
tags: self.tags.clone(),
terms: self.terms.clone(),
specific: ActionQuerySpecifics {
scheduled: Some(true),
..Default::default()
},
..Default::default()
specific: Default::default(),
},
None,
None,
&args.user,
PermissionLevel::Read.into(),
&all_tags,
),
list_full_for_user::<Procedure>(
ResourceQuery {
names: Default::default(),
templates: TemplatesQueryBehavior::Include,
tag_behavior: self.tag_behavior,
tags: self.tags.clone(),
terms: self.terms,
specific: ProcedureQuerySpecifics {
scheduled: Some(true),
..Default::default()
},
..Default::default()
specific: Default::default(),
},
None,
None,
&args.user,
PermissionLevel::Read.into(),
&all_tags,
@@ -110,51 +96,12 @@ impl Resolve<ReadArgs> for ListSchedules {
let (actions, procedures) =
tokio::join!(join_all(actions), join_all(procedures));
// The terms / scheduled filters are already applied
// at the db level by the queries above.
let mut schedules =
actions.into_iter().chain(procedures).collect::<Vec<_>>();
// The schedules are composed in memory across resource types,
// so all matching schedules are collected and sorted
// before applying pagination.
// All comparators fall back to name based sorting for equal
// sort keys, inside `compare`, so descending sorts are fully
// descending, matching the List<Resource> apis.
let compare: fn(&Schedule, &Schedule) -> Ordering =
match self.sort_by {
ScheduleSortBy::Name => |a, b| a.name.cmp(&b.name),
ScheduleSortBy::Schedule => |a, b| {
a.schedule
.cmp(&b.schedule)
.then_with(|| a.name.cmp(&b.name))
},
// Order unscheduled (None) last, matching the UI.
ScheduleSortBy::NextRun => |a, b| {
(a.next_scheduled_run.is_none(), a.next_scheduled_run)
.cmp(&(
b.next_scheduled_run.is_none(),
b.next_scheduled_run,
))
.then_with(|| a.name.cmp(&b.name))
},
ScheduleSortBy::Enabled => |a, b| {
a.enabled.cmp(&b.enabled).then_with(|| a.name.cmp(&b.name))
},
};
if self.sort_desc {
schedules.sort_by(|a, b| compare(b, a));
} else {
schedules.sort_by(compare);
}
let limit = list_limit(self.limit);
let skip = limit.saturating_mul(self.page) as usize;
let take = if limit == 0 {
usize::MAX
} else {
limit as usize
};
Ok(schedules.into_iter().skip(skip).take(take).collect())
Ok(
actions
.into_iter()
.chain(procedures)
.filter(|s| !s.schedule.is_empty())
.collect(),
)
}
}
+546 -113
View File
@@ -1,5 +1,5 @@
use std::{
cmp::Ordering,
cmp,
collections::HashMap,
sync::{Arc, OnceLock},
};
@@ -15,31 +15,47 @@ use database::mungos::{
use komodo_client::{
api::read::*,
entities::{
ResourceTarget,
deployment::Deployment,
docker::{
container::{
Container, ContainerListItem, ContainerStateStatusEnum,
},
image::{Image, ImageHistoryResponseItem},
network::Network,
volume::Volume,
},
permission::PermissionLevel,
server::{
Server, ServerActionState, ServerListItem, ServerSortBy,
Server, ServerActionState, ServerListItem, ServerQuery,
ServerState,
},
stats::{MinimalSystemStats, SystemInformation, SystemProcess},
stack::{Stack, StackServiceNames},
stats::{SystemInformation, SystemProcess},
update::Log,
},
};
use mogh_error::AddStatusCode;
use mogh_resolver::Resolve;
use periphery_client::api as periphery;
use periphery_client::api::{
self as periphery,
container::InspectContainer,
docker::{
ImageHistory, InspectImage, InspectNetwork, InspectVolume,
},
};
use reqwest::StatusCode;
use tokio::sync::Mutex;
use crate::{
helpers::{
periphery_client,
query::{get_all_tags, get_cached_server_state},
},
permission::get_check_permissions,
helpers::{periphery_client, query::get_all_tags},
permission::{get_check_permissions, list_resources_for_user},
resource,
stack::compose_container_match_regex,
state::{action_states, db_client, server_status_cache},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetServersSummary {
async fn resolve(
@@ -48,8 +64,6 @@ impl Resolve<ReadArgs> for GetServersSummary {
) -> mogh_error::Result<GetServersSummaryResponse> {
let servers = resource::list_for_user::<Server>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
@@ -111,89 +125,15 @@ impl Resolve<ReadArgs> for ListServers {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<ServerListItem> = match self
.sort_by
{
ServerSortBy::Name => resource::ListItemSort::Name,
ServerSortBy::Region => {
resource::ListItemSort::DbField("config.region")
}
ServerSortBy::Version => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.version
.cmp(&b.info.version)
.then_with(|| a.name.cmp(&b.name))
}))
}
ServerSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
ServerSortBy::Cpu => stats_sort(|stats| stats.cpu_perc as f64),
ServerSortBy::Memory => stats_sort(|stats| {
usage_percent(stats.mem_used_gb, stats.mem_total_gb)
}),
ServerSortBy::Disk => stats_sort(|stats| {
usage_percent(stats.disk_used_gb, stats.disk_total_gb)
}),
ServerSortBy::LoadAverage => {
stats_sort(|stats| stats.load_average.one)
}
ServerSortBy::Network => stats_sort(|stats| {
stats.network_ingress_bytes + stats.network_egress_bytes
}),
};
let servers = resource::list_items_for_user::<Server>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|server| {
states.is_empty() || states.contains(&server.info.state)
},
Ok(
resource::list_for_user::<Server>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
.await?;
Ok(servers)
}
}
/// Build an in memory sort on the list item stats,
/// matching the stats displayed on the server stats table.
/// Servers without stats (unreachable / disabled) order last.
fn stats_sort(
metric: fn(&MinimalSystemStats) -> f64,
) -> resource::ListItemSort<ServerListItem> {
resource::ListItemSort::InMemory(Box::new(move |a, b| {
match (a.info.stats.as_ref(), b.info.stats.as_ref()) {
(Some(a_stats), Some(b_stats)) => {
metric(a_stats).total_cmp(&metric(b_stats))
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
}
.then_with(|| a.name.cmp(&b.name))
}))
}
fn usage_percent(used: f64, total: f64) -> f64 {
if total > 0.0 {
100.0 * used / total
} else {
0.0
}
}
@@ -207,29 +147,12 @@ impl Resolve<ReadArgs> for ListFullServers {
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Server, _>(
resource::list_full_for_user::<Server>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|server| {
let states = states.clone();
async move {
if states.is_empty()
|| states
.contains(&get_cached_server_state(&server.id).await)
{
Some(server)
} else {
None
}
}
},
)
.await?,
)
@@ -416,7 +339,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
let curr_ts = unix_timestamp_ms() as i64;
let mut curr_ts = curr_ts
- curr_ts % granularity
- granularity * (page as i64).saturating_mul(STATS_PER_PAGE);
- granularity * STATS_PER_PAGE * page as i64;
for _ in 0..STATS_PER_PAGE {
ts_vec.push(curr_ts);
curr_ts -= granularity;
@@ -430,6 +353,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
},
FindOptions::builder()
.sort(doc! { "ts": -1 })
.skip(page as u64 * STATS_PER_PAGE as u64)
.limit(STATS_PER_PAGE)
.build(),
)
@@ -444,3 +368,512 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
Ok(res)
}
}
impl Resolve<ReadArgs> for ListDockerContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListDockerContainersResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.containers.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for ListAllDockerContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListAllDockerContainersResponse> {
let servers = resource::list_for_user::<Server>(
ServerQuery::builder().names(self.servers.clone()).build(),
user,
PermissionLevel::Read.into(),
&[],
)
.await?;
let mut containers = Vec::<ContainerListItem>::new();
for server in servers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
let Some(docker) = &cache.docker else {
continue;
};
let more = docker
.containers
.iter()
.filter(|container| {
self.containers.is_empty()
|| self.containers.contains(&container.name)
})
.cloned();
containers.extend(more);
}
Ok(containers)
}
}
impl Resolve<ReadArgs> for GetDockerContainersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetDockerContainersSummaryResponse> {
let servers = resource::list_full_for_user::<Server>(
Default::default(),
user,
PermissionLevel::Read.into(),
&[],
)
.await
.context("failed to get servers from db")?;
let mut res = GetDockerContainersSummaryResponse::default();
for server in servers {
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
for container in &docker.containers {
res.total += 1;
match container.state {
ContainerStateStatusEnum::Created
| ContainerStateStatusEnum::Paused
| ContainerStateStatusEnum::Exited => res.stopped += 1,
ContainerStateStatusEnum::Running => res.running += 1,
ContainerStateStatusEnum::Empty => res.unknown += 1,
_ => res.unhealthy += 1,
}
}
}
}
Ok(res)
}
}
impl Resolve<ReadArgs> for InspectDockerContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Container> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.inspect(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect container: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(InspectContainer {
name: self.container,
})
.await?;
Ok(res)
}
}
const MAX_LOG_LENGTH: u64 = 5000;
impl Resolve<ReadArgs> for GetContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Log> {
let GetContainerLog {
server,
container,
tail,
timestamps,
} = self;
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)
.await?
.request(periphery::container::GetContainerLog {
name: container,
tail: cmp::min(tail, MAX_LOG_LENGTH),
timestamps,
})
.await
.context("failed at call to periphery")?;
Ok(res)
}
}
impl Resolve<ReadArgs> for SearchContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Log> {
let SearchContainerLog {
server,
container,
terms,
combinator,
invert,
timestamps,
} = self;
let server = get_check_permissions::<Server>(
&server,
user,
PermissionLevel::Read.logs(),
)
.await?;
let res = periphery_client(&server)
.await?
.request(periphery::container::GetContainerLogSearch {
name: container,
terms,
combinator,
invert,
timestamps,
})
.await
.context("failed at call to periphery")?;
Ok(res)
}
}
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
// first check deployments
if let Ok(deployment) =
resource::get::<Deployment>(&self.container).await
{
return Ok(GetResourceMatchingContainerResponse {
resource: ResourceTarget::Deployment(deployment.id).into(),
});
}
// then check stacks
let stacks = list_resources_for_user::<Stack>(
doc! { "config.server_id": &server.id },
user,
PermissionLevel::Read.into(),
)
.await?;
// check matching stack
for stack in stacks {
for StackServiceNames {
service_name,
container_name,
..
} in stack
.info
.deployed_services
.unwrap_or(stack.info.latest_services)
{
let is_match = match compose_container_match_regex(&container_name)
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
{
Ok(regex) => regex,
Err(e) => {
warn!("{e:#}");
continue;
}
}.is_match(&self.container);
if is_match {
return Ok(GetResourceMatchingContainerResponse {
resource: ResourceTarget::Stack(stack.id).into(),
});
}
}
}
Ok(GetResourceMatchingContainerResponse { resource: None })
}
}
impl Resolve<ReadArgs> for ListDockerNetworks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListDockerNetworksResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.networks.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectDockerNetwork {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Network> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot inspect network: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(InspectNetwork { name: self.network })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListDockerImages {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListDockerImagesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.images.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectDockerImage {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Image> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!("Cannot inspect image: server is {:?}", cache.state)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(InspectImage { name: self.image })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListDockerImageHistory {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!(
"Cannot get image history: server is {:?}",
cache.state
)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(ImageHistory { name: self.image })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListDockerVolumes {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListDockerVolumesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.volumes.clone())
} else {
Ok(Vec::new())
}
}
}
impl Resolve<ReadArgs> for InspectDockerVolume {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Volume> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if cache.state != ServerState::Ok {
return Err(
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
.into(),
);
}
let res = periphery_client(&server)
.await?
.request(InspectVolume { name: self.volume })
.await?;
Ok(res)
}
}
impl Resolve<ReadArgs> for ListComposeProjects {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListComposeProjectsResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
PermissionLevel::Read.into(),
)
.await?;
let cache = server_status_cache()
.get_or_insert_default(&server.id)
.await;
if let Some(docker) = &cache.docker {
Ok(docker.projects.clone())
} else {
Ok(Vec::new())
}
}
}
// impl Resolve<ReadArgs> for ListAllTerminals {
// async fn resolve(
// self,
// args: &ReadArgs,
// ) -> Result<Self::Response, Self::Error> {
// // match self.tar
// let mut terminals = resource::list_full_for_user::<Server>(
// self.query, &args.user, &all_tags,
// )
// .await?
// .into_iter()
// .map(|server| async move {
// (
// list_terminals_inner(&server, self.fresh).await,
// (server.id, server.name),
// )
// })
// .collect::<FuturesUnordered<_>>()
// .collect::<Vec<_>>()
// .await
// .into_iter()
// .flat_map(|(terminals, server)| {
// let terminals = terminals.ok()?;
// Some((terminals, server))
// })
// .flat_map(|(terminals, (server_id, server_name))| {
// terminals.into_iter().map(move |info| {
// TerminalInfoWithServer::from_terminal_info(
// &server_id,
// &server_name,
// info,
// )
// })
// })
// .collect::<Vec<_>>();
// terminals.sort_by(|a, b| {
// a.server_name.cmp(&b.server_name).then(a.name.cmp(&b.name))
// });
// Ok(terminals)
// }
// }
+61 -205
View File
@@ -9,10 +9,7 @@ use komodo_client::{
container::Container, service::SwarmService, stack::SwarmStack,
},
permission::PermissionLevel,
stack::{
Stack, StackActionState, StackListItem, StackQuery,
StackService, StackSortBy, StackState,
},
stack::{Stack, StackActionState, StackListItem, StackState},
},
};
use mogh_error::AddStatusCodeError as _;
@@ -25,9 +22,7 @@ use reqwest::StatusCode;
use crate::{
helpers::{
periphery_client,
query::{get_all_tags, get_cached_stack_state},
swarm::swarm_request,
periphery_client, query::get_all_tags, swarm::swarm_request,
},
permission::get_check_permissions,
resource,
@@ -35,7 +30,7 @@ use crate::{
state::{action_states, stack_status_cache},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetStack {
async fn resolve(
@@ -53,128 +48,6 @@ impl Resolve<ReadArgs> for GetStack {
}
}
impl Resolve<ReadArgs> for ListStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Vec<StackListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let only_update_available = self.query.specific.update_available;
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<StackListItem> =
match self.sort_by {
StackSortBy::Name => resource::ListItemSort::Name,
StackSortBy::Source => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.files_on_host
.cmp(&b.info.files_on_host)
.then_with(|| {
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
})
.then_with(|| a.info.repo.cmp(&b.info.repo))
.then_with(|| a.name.cmp(&b.name))
}))
}
StackSortBy::Host => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
let host_a = if a.info.swarm_id.is_empty() {
&a.info.server_name
} else {
&a.info.swarm_name
};
let host_b = if b.info.swarm_id.is_empty() {
&b.info.server_name
} else {
&b.info.swarm_name
};
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
}))
}
StackSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| {
// Use ! with update available to order 'true' first
(!a.info.update_available())
.cmp(&!b.info.update_available())
})
.then_with(|| a.name.cmp(&b.name))
}))
}
};
let stacks = resource::list_items_for_user::<Stack>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|stack| {
(!only_update_available
|| stack
.info
.services
.iter()
.any(|service| service.update_available))
&& (states.is_empty() || states.contains(&stack.info.state))
},
)
.await?;
Ok(stacks)
}
}
impl Resolve<ReadArgs> for ListFullStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListFullStacksResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let states = self.query.specific.states.clone();
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user_filtered::<Stack, _>(
self.query,
limit,
self.page,
user,
PermissionLevel::Read.into(),
&all_tags,
|stack| {
let states = states.clone();
async move {
if states.is_empty()
|| states
.contains(&get_cached_stack_state(&stack.id).await)
{
Some(stack)
} else {
None
}
}
},
)
.await?,
)
}
}
impl Resolve<ReadArgs> for ListStackServices {
async fn resolve(
self,
@@ -199,75 +72,6 @@ impl Resolve<ReadArgs> for ListStackServices {
}
}
impl Resolve<ReadArgs> for ListAllStackServices {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListStackServicesResponse> {
let all_tags = if self.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let stacks = resource::list_for_user::<Stack>(
StackQuery::builder()
.names(self.stacks.clone())
.tags(self.tags)
.build(),
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?;
let mut services = Vec::<StackService>::new();
let mut skipped = 0;
let limit = list_limit(self.limit);
let limit_usize = limit as usize;
// Eg. page 1 skips until after 100 services, page 2 after 200.
let skip = limit.saturating_mul(self.page);
// Match terms case insensitively.
let terms = self
.terms
.iter()
.map(|term| term.to_lowercase())
.collect::<Vec<_>>();
for stack in stacks {
let cache =
stack_status_cache().get_or_insert_default(&stack.id).await;
let more = cache.curr.services
.iter()
.filter(|service| {
// Apply state filter if defined.
(self.state.is_empty() || self.state.contains(&service.state)) &&
// Apply terms filter if defined
(terms.is_empty()
// Match when all terms contained within a name.
|| {
let name = service.service.to_lowercase();
terms.iter().all(|term| name.contains(term))
})
});
for service in more {
if skipped < skip {
skipped += 1;
} else {
// push and maybe early return
services.push(service.clone());
if limit > 0 && services.len() >= limit_usize {
return Ok(services);
}
}
}
}
Ok(services)
}
}
impl Resolve<ReadArgs> for GetStackLog {
async fn resolve(
self,
@@ -535,8 +339,6 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
};
let stacks = resource::list_full_for_user::<Stack>(
self.query,
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -571,8 +373,6 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
};
let stacks = resource::list_full_for_user::<Stack>(
self.query,
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -595,6 +395,64 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
}
}
impl Resolve<ReadArgs> for ListStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<Vec<StackListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let only_update_available = self.query.specific.update_available;
let stacks = resource::list_for_user::<Stack>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?;
let stacks = if only_update_available {
stacks
.into_iter()
.filter(|stack| {
stack
.info
.services
.iter()
.any(|service| service.update_available)
})
.collect()
} else {
stacks
};
Ok(stacks)
}
}
impl Resolve<ReadArgs> for ListFullStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListFullStacksResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
Ok(
resource::list_full_for_user::<Stack>(
self.query,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?,
)
}
}
impl Resolve<ReadArgs> for GetStackActionState {
async fn resolve(
self,
@@ -623,8 +481,6 @@ impl Resolve<ReadArgs> for GetStacksSummary {
) -> mogh_error::Result<GetStacksSummaryResponse> {
let stacks = resource::list_full_for_user::<Stack>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+3 -30
View File
@@ -3,9 +3,7 @@ use komodo_client::{
api::read::*,
entities::{
permission::PermissionLevel,
swarm::{
Swarm, SwarmActionState, SwarmListItem, SwarmSortBy, SwarmState,
},
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
},
};
use mogh_resolver::Resolve;
@@ -17,7 +15,7 @@ use crate::{
state::{action_states, server_status_cache, swarm_status_cache},
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetSwarm {
async fn resolve(
@@ -45,32 +43,12 @@ impl Resolve<ReadArgs> for ListSwarms {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<SwarmListItem> =
match self.sort_by {
SwarmSortBy::Name => resource::ListItemSort::Name,
SwarmSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
Ok(
resource::list_items_for_user::<Swarm>(
resource::list_for_user::<Swarm>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|_| true,
)
.await?,
)
@@ -87,12 +65,9 @@ impl Resolve<ReadArgs> for ListFullSwarms {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user::<Swarm>(
self.query,
limit as i64,
self.page.saturating_mul(limit),
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -130,8 +105,6 @@ impl Resolve<ReadArgs> for GetSwarmsSummary {
) -> mogh_error::Result<GetSwarmsSummaryResponse> {
let swarms = resource::list_full_for_user::<Swarm>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
+3 -49
View File
@@ -5,7 +5,6 @@ use komodo_client::{
permission::PermissionLevel,
sync::{
ResourceSync, ResourceSyncActionState, ResourceSyncListItem,
ResourceSyncSortBy,
},
},
};
@@ -16,7 +15,7 @@ use crate::{
resource, state::action_states,
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetResourceSync {
async fn resolve(
@@ -44,52 +43,12 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
let sort_by: resource::ListItemSort<ResourceSyncListItem> =
match self.sort_by {
ResourceSyncSortBy::Name => resource::ListItemSort::Name,
ResourceSyncSortBy::Source => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.files_on_host
.cmp(&b.info.files_on_host)
.then_with(|| {
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
})
.then_with(|| a.info.repo.cmp(&b.info.repo))
.then_with(|| a.name.cmp(&b.name))
}))
}
ResourceSyncSortBy::Branch => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.branch
.cmp(&b.info.branch)
.then_with(|| a.name.cmp(&b.name))
}))
}
ResourceSyncSortBy::State => {
resource::ListItemSort::InMemory(Box::new(|a, b| {
a.info
.state
.cmp(&b.info.state)
.then_with(|| a.name.cmp(&b.name))
}))
}
};
Ok(
resource::list_items_for_user::<ResourceSync>(
resource::list_for_user::<ResourceSync>(
self.query,
resource::ListItemsQueryOptions {
limit,
page: self.page,
sort_desc: self.sort_desc,
sort_by,
},
user,
PermissionLevel::Read.into(),
&all_tags,
|_| true,
)
.await?,
)
@@ -106,12 +65,9 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
} else {
get_all_tags(None).await?
};
let limit = list_limit(self.limit);
Ok(
resource::list_full_for_user::<ResourceSync>(
self.query,
limit as i64,
self.page.saturating_mul(limit),
user,
PermissionLevel::Read.into(),
&all_tags,
@@ -150,8 +106,6 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
let resource_syncs =
resource::list_full_for_user::<ResourceSync>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.into(),
&[],
@@ -166,7 +120,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
for resource_sync in resource_syncs {
res.total += 1;
if !(resource_sync.info.pending_deploys.is_empty()
if !(resource_sync.info.pending_deploy.to_deploy == 0
&& resource_sync.info.resource_updates.is_empty()
&& resource_sync.info.variable_updates.is_empty()
&& resource_sync.info.user_group_updates.is_empty())
+56 -118
View File
@@ -1,5 +1,3 @@
use std::cmp::Ordering;
use anyhow::Context as _;
use futures_util::{
FutureExt, StreamExt as _, stream::FuturesUnordered,
@@ -11,7 +9,7 @@ use komodo_client::{
permission::PermissionLevel,
server::Server,
stack::Stack,
terminal::{Terminal, TerminalSortBy, TerminalTarget},
terminal::{Terminal, TerminalTarget},
user::User,
},
};
@@ -24,7 +22,7 @@ use crate::{
resource,
};
use super::{ReadArgs, list_limit};
use super::ReadArgs;
//
@@ -33,111 +31,57 @@ impl Resolve<ReadArgs> for ListTerminals {
self,
ReadArgs { user }: &ReadArgs,
) -> mogh_error::Result<ListTerminalsResponse> {
let mut terminals = match self.target {
None => {
list_all_terminals_for_user(user, self.use_names).await?
let Some(target) = self.target else {
return list_all_terminals_for_user(user, self.use_names).await;
};
match &target {
TerminalTarget::Server { server } => {
let server = server
.as_ref()
.context("Must provide 'target.params.server'")
.status_code(StatusCode::BAD_REQUEST)?;
let server = get_check_permissions::<Server>(
server,
user,
PermissionLevel::Read.terminal(),
)
.await?;
list_terminals_on_server(&server, Some(target)).await
}
TerminalTarget::Container { server, .. } => {
let server = get_check_permissions::<Server>(
server,
user,
PermissionLevel::Read.terminal(),
)
.await?;
list_terminals_on_server(&server, Some(target)).await
}
TerminalTarget::Stack { stack, .. } => {
let server = get_check_permissions::<Stack>(
stack,
user,
PermissionLevel::Read.terminal(),
)
.await?
.config
.server_id;
let server = resource::get::<Server>(&server).await?;
list_terminals_on_server(&server, Some(target)).await
}
TerminalTarget::Deployment { deployment } => {
let server = get_check_permissions::<Deployment>(
deployment,
user,
PermissionLevel::Read.terminal(),
)
.await?
.config
.server_id;
let server = resource::get::<Server>(&server).await?;
list_terminals_on_server(&server, Some(target)).await
}
Some(target) => match &target {
TerminalTarget::Server { server } => {
let server = server
.as_ref()
.context("Must provide 'target.params.server'")
.status_code(StatusCode::BAD_REQUEST)?;
let server = get_check_permissions::<Server>(
server,
user,
PermissionLevel::Read.terminal(),
)
.await?;
list_terminals_on_server(&server, Some(target)).await?
}
TerminalTarget::Container { server, .. } => {
let server = get_check_permissions::<Server>(
server,
user,
PermissionLevel::Read.terminal(),
)
.await?;
list_terminals_on_server(&server, Some(target)).await?
}
TerminalTarget::Stack { stack, .. } => {
let server = get_check_permissions::<Stack>(
stack,
user,
PermissionLevel::Read.terminal(),
)
.await?
.config
.server_id;
let server = resource::get::<Server>(&server).await?;
list_terminals_on_server(&server, Some(target)).await?
}
TerminalTarget::Deployment { deployment } => {
let server = get_check_permissions::<Deployment>(
deployment,
user,
PermissionLevel::Read.terminal(),
)
.await?
.config
.server_id;
let server = resource::get::<Server>(&server).await?;
list_terminals_on_server(&server, Some(target)).await?
}
},
};
// The terminals come from Periphery agents rather than the db,
// so the terms filter / sort / pagination are applied in memory.
if !self.terms.is_empty() {
let terms = self
.terms
.iter()
.map(|term| term.to_lowercase())
.collect::<Vec<_>>();
terminals.retain(|terminal| {
let name = terminal.name.to_lowercase();
terms.iter().all(|term| name.contains(term))
});
}
// All comparators fall back to name based sorting for equal
// sort keys, inside `compare`, so descending sorts are fully
// descending, matching the List<Resource> apis.
let compare: fn(&Terminal, &Terminal) -> Ordering =
match self.sort_by {
TerminalSortBy::Name => |a, b| a.name.cmp(&b.name),
TerminalSortBy::Target => |a, b| {
a.target.cmp(&b.target).then_with(|| a.name.cmp(&b.name))
},
TerminalSortBy::Command => |a, b| {
a.command.cmp(&b.command).then_with(|| a.name.cmp(&b.name))
},
TerminalSortBy::Size => |a, b| {
a.stored_size_kb
.total_cmp(&b.stored_size_kb)
.then_with(|| a.name.cmp(&b.name))
},
TerminalSortBy::Created => |a, b| {
a.created_at
.cmp(&b.created_at)
.then_with(|| a.name.cmp(&b.name))
},
};
if self.sort_desc {
terminals.sort_by(|a, b| compare(b, a));
} else {
terminals.sort_by(compare);
}
let limit = list_limit(self.limit);
let skip = limit.saturating_mul(self.page) as usize;
let take = if limit == 0 {
usize::MAX
} else {
limit as usize
};
Ok(terminals.into_iter().skip(skip).take(take).collect())
}
}
@@ -148,8 +92,6 @@ async fn list_all_terminals_for_user(
let (mut servers, stacks, deployments) = tokio::try_join!(
resource::list_full_for_user::<Server>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.terminal(),
&[]
@@ -161,16 +103,12 @@ async fn list_all_terminals_for_user(
.collect::<Vec<_>>())),
resource::list_full_for_user::<Stack>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.terminal(),
&[]
),
resource::list_full_for_user::<Deployment>(
Default::default(),
None,
None,
user,
PermissionLevel::Read.terminal(),
&[]
@@ -201,7 +139,7 @@ async fn list_all_terminals_for_user(
}
}
let terminals = servers
let mut terminals = servers
.into_iter()
.map(|(server, server_permission)| async move {
(
@@ -230,7 +168,6 @@ async fn list_all_terminals_for_user(
server_id.clone()
}),
};
terminal.target_name = Some(server_name.clone());
terminal
}),
TerminalTarget::Container { container, .. } => {
@@ -243,7 +180,6 @@ async fn list_all_terminals_for_user(
},
container,
};
terminal.target_name = Some(server_name.clone());
terminal
})
}
@@ -257,7 +193,6 @@ async fn list_all_terminals_for_user(
},
service,
};
terminal.target_name = Some(s.name.clone());
terminal
})
}
@@ -271,7 +206,6 @@ async fn list_all_terminals_for_user(
d.id.clone()
},
};
terminal.target_name = Some(d.name.clone());
terminal
},
)
@@ -286,6 +220,10 @@ async fn list_all_terminals_for_user(
.flatten()
.collect::<Vec<_>>();
terminals.sort_by(|a, b| {
a.target.cmp(&b.target).then(a.name.cmp(&b.name))
});
Ok(terminals)
}
+340 -123
View File
@@ -1,8 +1,5 @@
use anyhow::Context;
use database::{
bson::doc,
mungos::{find::find_collect, mongodb::options::FindOptions},
};
use database::mungos::find::find_collect;
use komodo_client::{
api::read::{
ExportAllResourcesToToml, ExportAllResourcesToTomlResponse,
@@ -28,7 +25,6 @@ use crate::{
resource,
state::db_client,
sync::{
replace_ids::ReplaceIds,
toml::{ToToml, convert_resource},
user_groups::{convert_user_groups, user_group_to_toml},
variables::variable_to_toml,
@@ -42,47 +38,122 @@ async fn get_all_targets(
user: &User,
) -> anyhow::Result<Vec<ResourceTarget>> {
let mut targets = Vec::<ResourceTarget>::new();
let all_tags = if tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
macro_rules! extend_targets {
($($Type:ident),* $(,)?) => {
$(
targets.extend(
resource::list_full_for_user::<$Type>(
ResourceQuery::builder().tags(tags).build(),
None,
None,
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::$Type(resource.id)),
);
)*
};
}
extend_targets!(
Alerter,
Builder,
Server,
Swarm,
Stack,
Deployment,
Build,
Repo,
Procedure,
Action,
ResourceSync,
targets.extend(
resource::list_full_for_user::<Alerter>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Alerter(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Builder>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Builder(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Server>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Server(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Stack>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Stack(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Deployment>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Deployment(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Build>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Build(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Repo>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Repo(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Procedure>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Procedure(resource.id)),
);
targets.extend(
resource::list_full_for_user::<Action>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
.map(|resource| ResourceTarget::Action(resource.id)),
);
targets.extend(
resource::list_full_for_user::<ResourceSync>(
ResourceQuery::builder().tags(tags).build(),
user,
PermissionLevel::Read.into(),
&all_tags,
)
.await?
.into_iter()
// These will already be filtered by [ExportResourcesToToml]
.map(|resource| ResourceTarget::ResourceSync(resource.id)),
);
Ok(targets)
}
@@ -116,7 +187,6 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
targets,
user_groups,
include_variables: self.include_variables,
existing: self.existing,
}
.resolve(args)
.await
@@ -132,64 +202,131 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
targets,
user_groups,
include_variables,
existing,
} = self;
let mut res = ResourcesToml::default();
let id_to_tags = get_id_to_tags(None).await?;
let ReadArgs { user } = args;
macro_rules! convert_target {
($id:expr, $Type:ident, $field:ident) => {{
let mut resource = get_check_permissions::<$Type>(
&$id,
user,
PermissionLevel::Read.into(),
)
.await?;
$Type::replace_ids(&mut resource.config);
let (deploy, after) = existing
.as_ref()
.and_then(|e| {
e.$field.iter().find(|r| r.name == resource.name)
})
.map(|r| (r.deploy, r.after.clone()))
.unwrap_or_default();
res.$field.push(convert_resource::<$Type>(
resource,
deploy,
after,
&id_to_tags,
));
}};
}
for target in targets {
match target {
ResourceTarget::Server(id) => {
convert_target!(id, Server, servers)
}
ResourceTarget::Swarm(id) => {
convert_target!(id, Swarm, swarms)
let mut swarm = get_check_permissions::<Swarm>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Swarm::replace_ids(&mut swarm);
res.swarms.push(convert_resource::<Swarm>(
swarm,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Server(id) => {
let mut server = get_check_permissions::<Server>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Server::replace_ids(&mut server);
res.servers.push(convert_resource::<Server>(
server,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Stack(id) => {
convert_target!(id, Stack, stacks)
let mut stack = get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Stack::replace_ids(&mut stack);
res.stacks.push(convert_resource::<Stack>(
stack,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Deployment(id) => {
convert_target!(id, Deployment, deployments)
let mut deployment = get_check_permissions::<Deployment>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Deployment::replace_ids(&mut deployment);
res.deployments.push(convert_resource::<Deployment>(
deployment,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Build(id) => {
convert_target!(id, Build, builds)
let mut build = get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Build::replace_ids(&mut build);
res.builds.push(convert_resource::<Build>(
build,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Repo(id) => {
let mut repo = get_check_permissions::<Repo>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Repo::replace_ids(&mut repo);
res.repos.push(convert_resource::<Repo>(
repo,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Repo(id) => convert_target!(id, Repo, repos),
ResourceTarget::Procedure(id) => {
convert_target!(id, Procedure, procedures)
let mut procedure = get_check_permissions::<Procedure>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Procedure::replace_ids(&mut procedure);
res.procedures.push(convert_resource::<Procedure>(
procedure,
false,
vec![],
&id_to_tags,
));
}
ResourceTarget::Action(id) => {
convert_target!(id, Action, actions)
}
ResourceTarget::Builder(id) => {
convert_target!(id, Builder, builders)
}
ResourceTarget::Alerter(id) => {
convert_target!(id, Alerter, alerters)
let mut action = get_check_permissions::<Action>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Action::replace_ids(&mut action);
res.actions.push(convert_resource::<Action>(
action,
false,
vec![],
&id_to_tags,
));
}
ResourceTarget::ResourceSync(id) => {
let mut sync = get_check_permissions::<ResourceSync>(
@@ -203,7 +340,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|| !sync.config.repo.is_empty()
|| !sync.config.linked_repo.is_empty())
{
ResourceSync::replace_ids(&mut sync.config);
ResourceSync::replace_ids(&mut sync);
res.resource_syncs.push(convert_resource::<ResourceSync>(
sync,
false,
@@ -212,6 +349,36 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
))
}
}
ResourceTarget::Builder(id) => {
let mut builder = get_check_permissions::<Builder>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Builder::replace_ids(&mut builder);
res.builders.push(convert_resource::<Builder>(
builder,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Alerter(id) => {
let mut alerter = get_check_permissions::<Alerter>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Alerter::replace_ids(&mut alerter);
res.alerters.push(convert_resource::<Alerter>(
alerter,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::System(_) => continue,
};
}
@@ -221,21 +388,18 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
.context("failed to add user groups")?;
if include_variables {
res.variables = find_collect(
&db_client().variables,
None,
FindOptions::builder().sort(doc! { "name": 1 }).build(),
)
.await
.context("failed to get variables from db")?
.into_iter()
.map(|mut variable| {
if !user.admin && variable.is_secret {
variable.value = "#".repeat(variable.value.len())
}
variable
})
.collect();
res.variables =
find_collect(&db_client().variables, None, None)
.await
.context("failed to get variables from db")?
.into_iter()
.map(|mut variable| {
if !user.admin && variable.is_secret {
variable.value = "#".repeat(variable.value.len())
}
variable
})
.collect();
}
let toml = serialize_resources_toml(res)
@@ -270,32 +434,85 @@ fn serialize_resources_toml(
) -> anyhow::Result<String> {
let mut toml = String::new();
macro_rules! serialize_resources {
($(($Type:ident, $field:ident, $header:literal)),* $(,)?) => {
$(
for resource in resources.$field {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str(concat!("[[",$header,"]]\n"));
$Type::push_to_toml_string(resource, &mut toml)?;
}
)*
};
for server in resources.servers {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[server]]\n");
Server::push_to_toml_string(server, &mut toml)?;
}
for stack in resources.stacks {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[stack]]\n");
Stack::push_to_toml_string(stack, &mut toml)?;
}
for deployment in resources.deployments {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[deployment]]\n");
Deployment::push_to_toml_string(deployment, &mut toml)?;
}
for build in resources.builds {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[build]]\n");
Build::push_to_toml_string(build, &mut toml)?;
}
for repo in resources.repos {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[repo]]\n");
Repo::push_to_toml_string(repo, &mut toml)?;
}
for procedure in resources.procedures {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[procedure]]\n");
Procedure::push_to_toml_string(procedure, &mut toml)?;
}
for action in resources.actions {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[action]]\n");
Action::push_to_toml_string(action, &mut toml)?;
}
for alerter in resources.alerters {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[alerter]]\n");
Alerter::push_to_toml_string(alerter, &mut toml)?;
}
for builder in resources.builders {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[builder]]\n");
Builder::push_to_toml_string(builder, &mut toml)?;
}
for resource_sync in resources.resource_syncs {
if !toml.is_empty() {
toml.push_str("\n\n##\n\n");
}
toml.push_str("[[resource_sync]]\n");
ResourceSync::push_to_toml_string(resource_sync, &mut toml)?;
}
serialize_resources!(
(Server, servers, "server"),
(Swarm, swarms, "swarm"),
(Stack, stacks, "stack"),
(Deployment, deployments, "deployment"),
(Build, builds, "build"),
(Repo, repos, "repo"),
(Procedure, procedures, "procedure"),
(Action, actions, "action"),
(Alerter, alerters, "alerter"),
(Builder, builders, "builder"),
(ResourceSync, resource_syncs, "resource_sync"),
);
for variable in &resources.variables {
if !toml.is_empty() {
+1 -3
View File
@@ -47,9 +47,7 @@ impl Resolve<ReadArgs> for ListUpdates {
query,
FindOptions::builder()
.sort(doc! { "start_ts": -1 })
.skip(
(self.page as u64).saturating_mul(UPDATES_PER_PAGE as u64),
)
.skip(self.page as u64 * UPDATES_PER_PAGE as u64)
.limit(UPDATES_PER_PAGE)
.build(),
)
+1 -12
View File
@@ -71,20 +71,9 @@ impl Resolve<ReadArgs> for ListUsers {
anyhow!("this route is only accessable by admins").into(),
);
}
let filter = match self.service_users {
komodo_client::api::read::ServiceUserQueryBehavior::Include => {
None
}
komodo_client::api::read::ServiceUserQueryBehavior::Exclude => {
Some(doc! { "config.type": { "$ne": "Service" } })
}
komodo_client::api::read::ServiceUserQueryBehavior::Only => {
Some(doc! { "config.type": "Service" })
}
};
let mut users = find_collect(
&db_client().users,
filter,
None,
FindOptions::builder().sort(doc! { "username": 1 }).build(),
)
.await
+49 -12
View File
@@ -1,8 +1,10 @@
use std::path::PathBuf;
use std::{path::PathBuf, time::Duration};
use anyhow::{Context, anyhow};
use database::mongo_indexed::doc;
use database::mungos::mongodb::bson::to_document;
use database::{
mongo_indexed::doc, mungos::mongodb::bson::oid::ObjectId,
};
use formatting::format_serror;
use komodo_client::{
api::write::*,
@@ -13,6 +15,7 @@ use komodo_client::{
builder::{Builder, BuilderConfig},
permission::PermissionLevel,
repo::Repo,
server::ServerState,
update::Update,
},
};
@@ -22,11 +25,12 @@ use periphery_client::api::build::{
};
use tokio::fs;
use crate::helpers::builder::connect_builder_periphery;
use crate::{
config::core_config,
connection::PeripheryConnectionArgs,
helpers::{
git_token,
git_token, periphery_client,
query::get_server_with_state,
update::{add_update, make_update},
},
periphery::PeripheryClient,
@@ -472,15 +476,48 @@ async fn get_on_host_periphery(
.await
.context("Failed to get builder")?;
if let BuilderConfig::Aws(_) = builder.config {
return Err(anyhow!(
"Files on host doesn't work with AWS builder"
));
match builder.config {
BuilderConfig::Aws(_) => {
Err(anyhow!("Files on host doesn't work with AWS builder"))
}
BuilderConfig::Url(config) => {
// TODO: Ensure connection is actually established.
// Builder id no good because it may be active for multiple connections.
let periphery = PeripheryClient::new(
PeripheryConnectionArgs::from_url_builder(
&ObjectId::new().to_hex(),
&config,
),
config.insecure_tls,
)
.await?;
// Poll for connection to be estalished
let mut err = None;
for _ in 0..10 {
tokio::time::sleep(Duration::from_secs(1)).await;
match periphery.health_check().await {
Ok(_) => return Ok(periphery),
Err(e) => err = Some(e),
};
}
Err(err.context("Missing error")?)
}
BuilderConfig::Server(config) => {
if config.server_id.is_empty() {
return Err(anyhow!(
"Builder is type server, but has no server attached"
));
}
let (server, state) =
get_server_with_state(&config.server_id).await?;
if state != ServerState::Ok {
return Err(anyhow!(
"Builder server is disabled or not reachable"
));
};
periphery_client(&server).await
}
}
connect_builder_periphery(build.name.clone(), None, builder, None)
.await
.map(|(periphery, _)| periphery)
}
/// The successful case will be included as Some(remote_contents).
+13 -67
View File
@@ -15,7 +15,6 @@ use komodo_client::{
docker::container::RestartPolicyNameEnum,
komodo_timestamp, optional_string,
permission::PermissionLevel,
resource::ResourceQuery,
server::{Server, ServerState},
to_container_compatible_name,
update::Update,
@@ -31,9 +30,7 @@ use crate::{
api::execute::{self, ExecuteRequest, ExecutionResult},
helpers::{
periphery_client,
query::{
get_all_tags, get_deployment_state, get_swarm_or_server,
},
query::{get_deployment_state, get_swarm_or_server},
registry_token,
update::{add_update, make_update, poll_update_until_complete},
},
@@ -303,24 +300,10 @@ impl Resolve<WriteArgs> for RenameDeployment {
let name = to_container_compatible_name(&self.name);
let state = get_deployment_state(&deployment.id).await?;
let container_state =
get_deployment_state(&deployment.id).await?;
// When no custom name is configured, the container / service
// follows the Deployment name, and must be kept matched:
// - Server mode: rename the container to the new name.
// - Swarm mode: services cannot be renamed, so the current
// service name is pinned in info.deployed_name, and stays
// in use until the next deploy recreates the service.
// A configured custom name is unaffected by Deployment rename.
let follows_name =
deployment.config.custom_name.trim().is_empty();
let deployed = state != DeploymentState::NotDeployed;
let rename_container = follows_name
&& deployed
&& !deployment.config.server_id.is_empty();
if rename_container && state == DeploymentState::Unknown {
if container_state == DeploymentState::Unknown {
return Err(
anyhow!(
"Cannot rename Deployment when container status is unknown"
@@ -329,51 +312,27 @@ impl Resolve<WriteArgs> for RenameDeployment {
);
}
// Includes the Unknown state, in case a service
// is still running under the current name.
let pin_deployed_name = follows_name
&& deployed
&& !deployment.config.swarm_id.is_empty()
&& deployment.info.deployed_name.is_empty();
let mut update =
make_update(&deployment, Operation::RenameDeployment, user);
let mut set =
doc! { "name": &name, "updated_at": komodo_timestamp() };
if rename_container {
// Container is renamed to the new name below.
set.insert("info.deployed_name", name.as_str());
} else if pin_deployed_name {
set.insert("info.deployed_name", deployment.deployed_name());
}
update_one_by_id(
&db_client().deployments,
&deployment.id,
database::mungos::update::Update::Set(set),
database::mungos::update::Update::Set(
doc! { "name": &name, "updated_at": komodo_timestamp() },
),
None,
)
.await
.context("Failed to update Deployment name on db")?;
if pin_deployed_name {
update.push_simple_log(
"Pin Service Name",
format!(
"Swarm services cannot be renamed, so the current service name '{}' stays in use until the next deploy recreates the service under the new name.",
deployment.deployed_name()
),
);
}
if rename_container {
if container_state != DeploymentState::NotDeployed {
let server =
resource::get::<Server>(&deployment.config.server_id).await?;
let log = periphery_client(&server)
.await?
.request(api::container::RenameContainer {
curr_name: deployment.deployed_name().to_string(),
curr_name: deployment.name.clone(),
new_name: name.clone(),
})
.await
@@ -477,7 +436,7 @@ pub async fn check_deployment_for_update_inner(
update_available: false,
});
}
let domain = extract_registry_domain(image)?;
let domain = extract_registry_domain(&image)?;
let account =
optional_string(&deployment.config.image_registry_account);
let token = if let Some(account) = &account {
@@ -499,14 +458,13 @@ pub async fn check_deployment_for_update_inner(
};
let latest_digest = image_digest_cache()
.get(swarm_or_server, image, account, token)
.get(&swarm_or_server, &image, account, token)
.await?;
resource::update_info::<Deployment>(
&deployment.id,
&DeploymentInfo {
latest_image_digest: latest_digest.clone(),
deployed_name: deployment.info.deployed_name.clone(),
},
)
.await?;
@@ -663,7 +621,6 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
fields(
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
skip_auto_update = self.skip_auto_update,
wait_for_auto_update = self.wait_for_auto_update,
)
@@ -672,23 +629,12 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
self,
WriteArgs { user }: &WriteArgs,
) -> Result<Self::Response, Self::Error> {
let all_tags = if self.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let deployments = list_full_for_user_using_pattern::<Deployment>(
&self.pattern,
ResourceQuery {
tags: self.tags,
..Default::default()
},
None,
None,
Default::default(),
user,
PermissionLevel::Execute.into(),
&all_tags,
&[],
)
.await?;
+11 -30
View File
@@ -54,7 +54,7 @@ pub struct WriteArgs {
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[strum_discriminants(name(WriteRequestMethod), derive(Display))]
#[strum_discriminants(name(WriteRequestVariant), derive(Display))]
#[args(WriteArgs)]
#[response(Response)]
#[error(mogh_error::Error)]
@@ -211,12 +211,9 @@ pub enum WriteRequest {
CreateGitProviderAccount(CreateGitProviderAccount),
UpdateGitProviderAccount(UpdateGitProviderAccount),
DeleteGitProviderAccount(DeleteGitProviderAccount),
#[serde(alias = "CreateDockerRegistryAccount")]
CreateImageRegistryAccount(CreateImageRegistryAccount),
#[serde(alias = "UpdateDockerRegistryAccount")]
UpdateImageRegistryAccount(UpdateImageRegistryAccount),
#[serde(alias = "DeleteDockerRegistryAccount")]
DeleteImageRegistryAccount(DeleteImageRegistryAccount),
CreateDockerRegistryAccount(CreateDockerRegistryAccount),
UpdateDockerRegistryAccount(UpdateDockerRegistryAccount),
DeleteDockerRegistryAccount(DeleteDockerRegistryAccount),
// ==== ALERT ====
CloseAlert(CloseAlert),
@@ -258,35 +255,19 @@ async fn task(
request: WriteRequest,
user: User,
) -> mogh_error::Result<axum::response::Response> {
let task_id = Uuid::new_v4();
let method: WriteRequestMethod = (&request).into();
let req_id = Uuid::new_v4();
let variant: WriteRequestVariant = (&request).into();
let user_id = user.id.clone();
let username = user.username.clone();
if !matches!(
request,
WriteRequest::SetLastSeenUpdate(_)
| WriteRequest::PushRecentlyViewed(_)
) {
info!(
task_id = task_id.to_string(),
method = method.to_string(),
user_id,
username,
"WRITE REQUEST",
);
}
info!(
"WRITE REQUEST {req_id} | METHOD: {variant} | USER: {} ({})",
user.username, user.id
);
let res = request.resolve(&WriteArgs { user }).await;
if let Err(e) = &res {
warn!(
task_id = task_id.to_string(),
method = method.to_string(),
user_id,
username,
"WRITE REQUEST | ERROR: {:#}",
"WRITE REQUEST {req_id} | METHOD: {variant} | ERROR: {:#}",
e.error
);
}
+5 -38
View File
@@ -15,10 +15,7 @@ use mogh_pki::EncodedKeyPair;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use crate::{
api::write::WriteArgs, helpers::query::get_all_tags,
state::db_client,
};
use crate::{api::write::WriteArgs, state::db_client};
//
@@ -56,21 +53,6 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
)?
.public
.into_inner();
let tags = if self.tags.is_empty() {
self.tags
} else {
// fix_tags by ensuring existence, and force replace with tag name.
let all_tags = get_all_tags(None).await?;
self
.tags
.into_iter()
.filter_map(|tag| {
let tag =
all_tags.iter().find(|t| t.id == tag || t.name == tag)?;
Some(tag.name.clone())
})
.collect()
};
let onboarding_key = OnboardingKey {
public_key,
name: self.name,
@@ -78,8 +60,8 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
onboarded: Default::default(),
created_at: komodo_timestamp(),
expires: self.expires,
tags,
privileged: self.privileged,
tags: self.tags,
fix_existing_servers: self.fix_existing_servers,
copy_server: self.copy_server,
create_builder: self.create_builder,
};
@@ -157,26 +139,11 @@ impl Resolve<WriteArgs> for UpdateOnboardingKey {
}
if let Some(tags) = self.tags {
let tags = if tags.is_empty() {
tags
} else {
// fix_tags by ensuring existence, and force replace with tag name.
let all_tags = get_all_tags(None).await?;
tags
.into_iter()
.filter_map(|tag| {
let tag = all_tags
.iter()
.find(|t| t.id == tag || t.name == tag)?;
Some(tag.name.clone())
})
.collect()
};
update.insert("tags", tags);
}
if let Some(privileged) = self.privileged {
update.insert("privileged", privileged);
if let Some(fix_existing_servers) = self.fix_existing_servers {
update.insert("fix_existing_servers", fix_existing_servers);
}
if let Some(copy_server) = self.copy_server {
+2 -2
View File
@@ -347,9 +347,9 @@ async fn extract_resource_target_with_validation(
.find_one(filter)
.await
.context("Failed to query db for swarms")?
.context("No matching swarm found")?
.context("No matching server found")?
.id;
Ok((ResourceTargetVariant::Swarm, id))
Ok((ResourceTargetVariant::Server, id))
}
ResourceTarget::Server(ident) => {
let filter = match ObjectId::from_str(ident) {
+8 -8
View File
@@ -7,7 +7,7 @@ use komodo_client::{
api::write::*,
entities::{
Operation, ResourceTarget,
provider::{GitProviderAccount, ImageRegistryAccount},
provider::{DockerRegistryAccount, GitProviderAccount},
},
};
use mogh_error::AddStatusCodeError;
@@ -248,7 +248,7 @@ impl Resolve<WriteArgs> for DeleteGitProviderAccount {
}
}
impl Resolve<WriteArgs> for CreateImageRegistryAccount {
impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
#[instrument(
"CreateDockerRegistryAccount",
skip_all,
@@ -261,7 +261,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<CreateImageRegistryAccountResponse> {
) -> mogh_error::Result<CreateDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!(
@@ -271,7 +271,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
);
}
let mut account: ImageRegistryAccount = self.account.into();
let mut account: DockerRegistryAccount = self.account.into();
if account.domain.is_empty() {
return Err(
@@ -326,7 +326,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
}
}
impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
#[instrument(
"UpdateDockerRegistryAccount",
skip_all,
@@ -340,7 +340,7 @@ impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
async fn resolve(
mut self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<UpdateImageRegistryAccountResponse> {
) -> mogh_error::Result<UpdateDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can update docker registry accounts")
@@ -423,7 +423,7 @@ impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
}
}
impl Resolve<WriteArgs> for DeleteImageRegistryAccount {
impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
#[instrument(
"DeleteDockerRegistryAccount",
skip_all,
@@ -435,7 +435,7 @@ impl Resolve<WriteArgs> for DeleteImageRegistryAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<DeleteImageRegistryAccountResponse> {
) -> mogh_error::Result<DeleteDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can delete docker registry accounts")
+11 -77
View File
@@ -19,11 +19,7 @@ use komodo_client::{
all_logs_success, komodo_timestamp,
permission::PermissionLevel,
repo::Repo,
resource::ResourceQuery,
stack::{
Stack, StackInfo, StackServiceNames, StackServiceWithUpdate,
StackState,
},
stack::{Stack, StackInfo, StackServiceWithUpdate, StackState},
update::Update,
user::{auto_redeploy_user, stack_user, system_user},
},
@@ -40,7 +36,7 @@ use crate::{
api::execute::{self, ExecuteRequest, ExecutionResult},
config::core_config,
helpers::{
query::{get_all_tags, get_swarm_or_server},
query::get_swarm_or_server,
stack_git_token, swarm_or_server_request,
update::{add_update, make_update, poll_update_until_complete},
},
@@ -559,25 +555,15 @@ impl Resolve<WriteArgs> for RefreshStackCache {
&mut services,
) {
warn!(
stack = stack.id,
stack_name = stack.name,
"Failed to extract stack services | {e:#}",
"failed to extract stack services, things won't works correctly. stack: {} | {e:#}",
stack.name
);
}
}
(services, Some(contents), Some(errors), None, None)
} else {
// This path is reached if the swarm / server is not available.
// It carries over the last successful poll.
(
stack.info.latest_services,
stack.info.remote_contents,
stack.info.remote_errors,
// Files on host can set hash / message back to None.
None,
None,
)
(vec![], None, None, None, None)
}
} else if !repo_empty {
// ================
@@ -780,18 +766,12 @@ pub async fn check_stack_for_update_inner(
if image.is_empty() ||
// Images with a hardcoded digest can't have update.
image.contains('@') ||
// Services explicitly excluded from global auto-update checks.
// Manual checks should still evaluate all services.
(wait_for_auto_update &&
stack.config.auto_update_skip_services.contains(
&service.service_name,
))
image.contains('@')
{
service.image_digest = None;
continue;
}
match cache.get(swarm_or_server, image, None, None).await {
match cache.get(&swarm_or_server, image, None, None).await {
Ok(digest) => service.image_digest = Some(digest),
Err(e) => {
warn!(
@@ -825,11 +805,6 @@ pub async fn check_stack_for_update_inner(
services: extract_services_from_stack(&stack)
.into_iter()
.map(|service| StackServiceWithUpdate {
latest_image: find_latest_image(
&service.service_name,
&service.image,
&stack.info.latest_services,
),
service: service.service_name,
image: service.image,
update_available: false,
@@ -850,11 +825,6 @@ pub async fn check_stack_for_update_inner(
.services
.iter()
.map(|service| StackServiceWithUpdate {
latest_image: find_latest_image(
&service.service,
&service.image,
&stack.info.latest_services,
),
service: service.service.clone(),
image: service.image.clone(),
update_available: false,
@@ -870,11 +840,6 @@ pub async fn check_stack_for_update_inner(
service: service.service.clone(),
image: service.image.clone(),
update_available: false,
latest_image: find_latest_image(
&service.service,
&service.image,
&stack.info.latest_services,
),
};
let Some(current_digests) = &service.image_digests else {
@@ -896,7 +861,7 @@ pub async fn check_stack_for_update_inner(
};
service_with_update.update_available =
latest_digest.update_available(current_digests);
latest_digest.update_available(&current_digests);
if service_with_update.update_available
&& (skip_auto_update || !stack.config.auto_update)
@@ -965,10 +930,7 @@ pub async fn check_stack_for_update_inner(
.retain(|(stack_id, _)| stack_id != &stack.id)
.await;
let deploy_services = if stack.config.auto_update_all_services
// Swarm stacks don't support individual service deploy
|| !stack.config.swarm_id.is_empty()
{
let deploy_services = if stack.config.auto_update_all_services {
Vec::new()
} else {
services_with_update
@@ -1050,22 +1012,6 @@ pub async fn check_stack_for_update_inner(
})
}
fn find_latest_image(
service_name: &str,
current_image: &str,
latest_services: &[StackServiceNames],
) -> Option<String> {
latest_services.iter().find_map(|latest| {
if latest.service_name == service_name
&& latest.image != current_image
{
Some(latest.image.clone())
} else {
None
}
})
}
//
impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
@@ -1075,7 +1021,6 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
fields(
operator = user.id,
pattern = self.pattern,
tags = self.tags.join(","),
skip_auto_update = self.skip_auto_update,
wait_for_auto_update = self.wait_for_auto_update,
)
@@ -1084,23 +1029,12 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
self,
WriteArgs { user }: &WriteArgs,
) -> Result<Self::Response, Self::Error> {
let all_tags = if self.tags.is_empty() {
vec![]
} else {
get_all_tags(None).await?
};
let stacks = list_full_for_user_using_pattern::<Stack>(
&self.pattern,
ResourceQuery {
tags: self.tags,
..Default::default()
},
None,
None,
Default::default(),
user,
PermissionLevel::Execute.into(),
&all_tags,
&[],
)
.await?;
+105 -46
View File
@@ -26,8 +26,7 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
swarm::Swarm,
sync::{ResourceSync, ResourceSyncInfo},
sync::{ResourceSync, ResourceSyncInfo, SyncDeployUpdate},
to_path_compatible_name,
update::{Log, Update},
user::sync_user,
@@ -516,20 +515,11 @@ impl Resolve<WriteArgs> for CommitSync {
None
};
// Get the latest existing resources to preserve any meta values
let RemoteResources { resources, .. } =
crate::sync::remote::get_remote_resources(&sync, repo.as_ref())
.await
.context("failed to get remote resources")?;
let res = ExportAllResourcesToToml {
include_resources: sync.config.include_resources,
tags: sync.config.match_tags.clone(),
include_variables: sync.config.include_variables,
include_user_groups: sync.config.include_user_groups,
existing: resources
.inspect_err(|e| warn!("Existing resource TOML is unavailable, resource meta will not be preserved | ERROR: {e:#}"))
.ok(),
}
.resolve(&ReadArgs {
user: sync_user().to_owned(),
@@ -785,40 +775,110 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
let mut diffs = Vec::new();
macro_rules! push_updates {
($(($Type:ident, $field:ident)),* $(,)?) => {
$(
push_updates_for_view::<$Type>(
resources.$field,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
)*
};
}
// New resource types need to be added here manually.
push_updates!(
(Server, servers),
(Swarm, swarms),
(Stack, stacks),
(Deployment, deployments),
(Build, builds),
(Repo, repos),
(Procedure, procedures),
(Action, actions),
(Builder, builders),
(Alerter, alerters),
(ResourceSync, resource_syncs),
);
push_updates_for_view::<Server>(
resources.servers,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Stack>(
resources.stacks,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Deployment>(
resources.deployments,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Build>(
resources.builds,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Repo>(
resources.repos,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Procedure>(
resources.procedures,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Action>(
resources.actions,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Builder>(
resources.builders,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<Alerter>(
resources.alerters,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
push_updates_for_view::<ResourceSync>(
resources.resource_syncs,
delete,
None,
None,
&id_to_tags,
&sync.config.match_tags,
&mut diffs,
)
.await?;
(diffs, deploy_updates)
} else {
Default::default()
(Vec::new(), SyncDeployUpdate::default())
};
let variable_updates = if sync.config.include_variables {
@@ -852,7 +912,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
let (
resource_updates,
(pending_deploys, pending_deploy_error),
deploy_updates,
variable_updates,
user_group_updates,
pending_error,
@@ -868,7 +928,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
};
let has_updates = !resource_updates.is_empty()
|| !pending_deploys.is_empty()
|| !deploy_updates.to_deploy == 0
|| !variable_updates.is_empty()
|| !user_group_updates.is_empty();
@@ -880,8 +940,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
remote_errors: sync.info.remote_errors,
pending_hash: sync.info.pending_hash,
pending_message: sync.info.pending_message,
pending_deploys,
pending_deploy_error,
pending_deploy: deploy_updates,
resource_updates,
variable_updates,
user_group_updates,
+31 -51
View File
@@ -3,12 +3,8 @@ use futures_util::{StreamExt as _, stream::FuturesUnordered};
use komodo_client::{
api::write::*,
entities::{
NoData,
deployment::Deployment,
permission::PermissionLevel,
server::Server,
stack::Stack,
terminal::{Terminal, TerminalTarget},
NoData, deployment::Deployment, permission::PermissionLevel,
server::Server, stack::Stack, terminal::TerminalTarget,
user::User,
},
};
@@ -51,35 +47,30 @@ impl Resolve<WriteArgs> for CreateTerminal {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<Terminal> {
) -> mogh_error::Result<NoData> {
match self.target.clone() {
TerminalTarget::Server { server } => {
let server = server
.context("Must provide 'target.params.server'")
.status_code(StatusCode::BAD_REQUEST)?;
create_server_terminal(self, server, user)
.await
.map_err(Into::into)
create_server_terminal(self, server, user).await?;
}
TerminalTarget::Container { server, container } => {
create_container_terminal(self, server, container, user)
.await
.map_err(Into::into)
.await?;
}
TerminalTarget::Stack { stack, service } => {
let service = service
.context("Must provide 'target.params.service'")
.status_code(StatusCode::BAD_REQUEST)?;
create_stack_service_terminal(self, stack, service, user)
.await
.map_err(Into::into)
.await?;
}
TerminalTarget::Deployment { deployment } => {
create_deployment_terminal(self, deployment, user)
.await
.map_err(Into::into)
create_deployment_terminal(self, deployment, user).await?;
}
}
};
Ok(NoData {})
}
}
@@ -93,7 +84,7 @@ async fn create_server_terminal(
}: CreateTerminal,
server: String,
user: &User,
) -> anyhow::Result<Terminal> {
) -> anyhow::Result<()> {
let server = get_check_permissions::<Server>(
&server,
user,
@@ -103,7 +94,7 @@ async fn create_server_terminal(
let periphery = periphery_client(&server).await?;
let mut terminal = periphery
periphery
.request(api::terminal::CreateServerTerminal {
name,
command,
@@ -112,12 +103,7 @@ async fn create_server_terminal(
.await
.context("Failed to create Server Terminal on Periphery")?;
// Fix server terminal target with server id
terminal.target = TerminalTarget::Server {
server: Some(server.id),
};
Ok(terminal)
Ok(())
}
async fn create_container_terminal(
@@ -125,7 +111,7 @@ async fn create_container_terminal(
server: String,
container: String,
user: &User,
) -> anyhow::Result<Terminal> {
) -> anyhow::Result<()> {
let server = get_check_permissions::<Server>(
&server,
user,
@@ -141,7 +127,7 @@ async fn create_stack_service_terminal(
stack: String,
service: String,
user: &User,
) -> anyhow::Result<Terminal> {
) -> anyhow::Result<()> {
let (_, periphery, container) =
get_stack_service_periphery_container(&stack, &service, user)
.await?;
@@ -152,7 +138,7 @@ async fn create_deployment_terminal(
req: CreateTerminal,
deployment: String,
user: &User,
) -> anyhow::Result<Terminal> {
) -> anyhow::Result<()> {
let (_, periphery, container) =
get_deployment_periphery_container(&deployment, user).await?;
create_container_terminal_inner(req, &periphery, container).await
@@ -171,10 +157,10 @@ impl Resolve<WriteArgs> for DeleteTerminal {
)
)]
async fn resolve(
mut self,
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<NoData> {
let server = match &mut self.target {
let server = match &self.target {
TerminalTarget::Server { server } => {
let server = server
.as_ref()
@@ -195,31 +181,27 @@ impl Resolve<WriteArgs> for DeleteTerminal {
)
.await?
}
TerminalTarget::Stack {
stack: stack_id, ..
} => {
let stack = get_check_permissions::<Stack>(
stack_id,
TerminalTarget::Stack { stack, .. } => {
let server = get_check_permissions::<Stack>(
stack,
user,
PermissionLevel::Read.terminal(),
)
.await?;
// Must fix any incoming stack name to id
*stack_id = stack.id;
resource::get::<Server>(&stack.config.server_id).await?
.await?
.config
.server_id;
resource::get::<Server>(&server).await?
}
TerminalTarget::Deployment {
deployment: deployment_id,
} => {
let deployment = get_check_permissions::<Deployment>(
deployment_id,
TerminalTarget::Deployment { deployment } => {
let server = get_check_permissions::<Deployment>(
deployment,
user,
PermissionLevel::Read.terminal(),
)
.await?;
// Must fix any incoming deployment name to id
*deployment_id = deployment.id;
resource::get::<Server>(&deployment.config.server_id).await?
.await?
.config
.server_id;
resource::get::<Server>(&server).await?
}
};
@@ -293,8 +275,6 @@ impl Resolve<WriteArgs> for BatchDeleteAllTerminals {
resource::list_full_for_user::<Server>(
self.query,
None,
None,
user,
PermissionLevel::Read.terminal(),
&all_tags,
-15
View File
@@ -36,15 +36,6 @@ use super::WriteArgs;
const RECENTLY_VIEWED_MAX: usize = 10;
impl Resolve<WriteArgs> for PushRecentlyViewed {
#[instrument(
"PushRecentlyViewed",
level = "debug",
skip_all,
fields(
user_id = user.id,
resource = format!("{:?}", self.resource)
)
)]
async fn resolve(
self,
WriteArgs { user, .. }: &WriteArgs,
@@ -88,12 +79,6 @@ impl Resolve<WriteArgs> for PushRecentlyViewed {
//
impl Resolve<WriteArgs> for SetLastSeenUpdate {
#[instrument(
"SetLastSeenUpdate",
level = "debug",
skip_all,
fields(user_id = user.id)
)]
async fn resolve(
self,
WriteArgs { user, .. }: &WriteArgs,
-3
View File
@@ -24,9 +24,6 @@ use crate::{
mod terminal;
mod update;
pub(super) const WS_KEEP_ALIVE_INTERVAL: std::time::Duration =
std::time::Duration::from_secs(30);
pub fn router() -> Router {
Router::new()
// Periphery facing
+6 -35
View File
@@ -5,7 +5,6 @@ use axum::{
response::IntoResponse,
};
use bytes::Bytes;
use colored::Colorize;
use futures_util::{SinkExt, StreamExt as _};
use komodo_client::{
api::terminal::ConnectTerminalQuery, entities::user::User,
@@ -117,25 +116,9 @@ async fn forward_ws_channel(
};
let periphery_to_core = async {
let mut keep_alive =
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
loop {
// Already adheres to cancellation token
let recv = tokio::select! {
// Websocket keep-alive ping
_ = keep_alive.tick() => {
if client_send
.send(ws::Message::Ping(Bytes::new()))
.await
.is_err()
{
break;
}
continue;
}
recv = periphery_receiver.recv() => recv,
};
match recv {
match periphery_receiver.recv().await {
Ok(Ok(bytes)) => {
if let Err(e) =
client_send.send(ws::Message::Binary(bytes.into())).await
@@ -145,26 +128,14 @@ async fn forward_ws_channel(
};
}
Ok(Err(e)) => {
let message = format!("{}: {e:#}", "ERROR".red().bold());
if message.contains("pty exited") {
let _ = client_send
.send(ws::Message::text(format!(
"\n{} {}",
"pty".bold(),
"exited".red().bold()
)))
.await;
} else {
let _ = client_send
.send(ws::Message::text(message.replace('\n', "\r\n")))
.await;
}
let _ = client_send
.send(ws::Message::text(format!("{e:#}")))
.await;
break;
}
Err(_) => {
let _ = client_send
.send(ws::Message::text("\r\n\nSTREAM EOF"))
.await;
let _ =
client_send.send(ws::Message::text("STREAM EOF")).await;
break;
}
}
+1 -11
View File
@@ -3,7 +3,6 @@ use axum::{
extract::{WebSocketUpgrade, ws::Message},
response::IntoResponse,
};
use bytes::Bytes;
use futures_util::{SinkExt, StreamExt};
use komodo_client::entities::{
ResourceTarget, permission::PermissionLevel, user::User,
@@ -39,19 +38,10 @@ pub async fn handler(
let cancel_clone = cancel.clone();
tokio::spawn(async move {
let mut keep_alive =
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
loop {
// poll for updates off the receiver / await cancel / send keep-alive ping.
// poll for updates off the receiver / await cancel.
let update = select! {
_ = cancel_clone.cancelled() => break,
_ = keep_alive.tick() => {
if ws_sender.send(Message::Ping(Bytes::new())).await.is_err() {
cancel_clone.cancel();
break;
}
continue;
}
update = receiver.recv() => {update.expect("failed to recv update msg")}
};
+1 -16
View File
@@ -148,7 +148,7 @@ impl AuthImpl for KomodoAuthImpl {
fn post_link_redirect(&self) -> &str {
static POST_LINK_REDIRECT: LazyLock<String> =
LazyLock::new(|| format!("{}/profile", core_config().host));
LazyLock::new(|| format!("{}/settings", core_config().host));
&POST_LINK_REDIRECT
}
@@ -205,20 +205,6 @@ impl AuthImpl for KomodoAuthImpl {
core_config().disable_user_registration
}
fn local_registration_disabled(&self) -> bool {
let config = core_config();
config
.disable_local_user_registration
.unwrap_or(config.disable_user_registration)
}
fn oidc_registration_disabled(&self) -> bool {
let config = core_config();
config
.disable_oidc_user_registration
.unwrap_or(config.disable_user_registration)
}
fn validate_username(
&self,
username: &str,
@@ -362,7 +348,6 @@ impl AuthImpl for KomodoAuthImpl {
additional_audiences: config
.oidc_additional_audiences
.clone(),
auto_redirect: config.oidc_auto_redirect,
}
});
Some(&OIDC_CONFIG)
+1 -1
View File
@@ -88,7 +88,7 @@ pub async fn launch_ec2_instance(
port: _,
use_https: _,
git_providers: _,
image_registries: _,
docker_registries: _,
secrets: _,
} = config;
let instance_type = handle_unknown_instance_type(
+2 -3
View File
@@ -2,9 +2,8 @@ pub mod aws;
#[derive(Debug)]
pub enum BuildCleanupData {
/// Store the builder id if it needs
/// to be removed from builder_usage_cache.
Server(Option<String>),
/// Nothing to clean up
Server,
/// Cleanup Periphery connection
Url,
/// Clean up AWS instance
+4 -38
View File
@@ -235,9 +235,6 @@ pub fn core_config() -> &'static CoreConfig {
env.komodo_oidc_additional_audiences,
)
.unwrap_or(config.oidc_additional_audiences),
oidc_auto_redirect: env
.komodo_oidc_auto_redirect
.unwrap_or(config.oidc_auto_redirect),
google_oauth: NamedOauthConfig {
enabled: env
.komodo_google_oauth_enabled
@@ -315,21 +312,6 @@ pub fn core_config() -> &'static CoreConfig {
session_allow_cross_site: env
.komodo_session_allow_cross_site
.unwrap_or(config.session_allow_cross_site),
x_content_type_options: env
.komodo_x_content_type_options
.unwrap_or(config.x_content_type_options),
x_frame_options: env
.komodo_x_frame_options
.unwrap_or(config.x_frame_options),
x_xss_protection: env
.komodo_x_xss_protection
.unwrap_or(config.x_xss_protection),
referrer_policy: env
.komodo_referrer_policy
.unwrap_or(config.referrer_policy),
content_security_policy: env
.komodo_content_security_policy
.unwrap_or(config.content_security_policy),
resource_poll_interval: env
.komodo_resource_poll_interval
.unwrap_or(config.resource_poll_interval),
@@ -348,9 +330,6 @@ pub fn core_config() -> &'static CoreConfig {
transparent_mode: env
.komodo_transparent_mode
.unwrap_or(config.transparent_mode),
default_pagination_limit: env
.komodo_default_pagination_limit
.unwrap_or(config.default_pagination_limit),
ui_write_disabled: env
.komodo_ui_write_disabled
.unwrap_or(config.ui_write_disabled),
@@ -366,12 +345,6 @@ pub fn core_config() -> &'static CoreConfig {
disable_user_registration: env
.komodo_disable_user_registration
.unwrap_or(config.disable_user_registration),
disable_local_user_registration: env
.komodo_disable_local_user_registration
.or(config.disable_local_user_registration),
disable_oidc_user_registration: env
.komodo_disable_oidc_user_registration
.or(config.disable_oidc_user_registration),
disable_non_admin_create: env
.komodo_disable_non_admin_create
.unwrap_or(config.disable_non_admin_create),
@@ -402,9 +375,6 @@ pub fn core_config() -> &'static CoreConfig {
.komodo_logging_location
.unwrap_or(config.logging.location),
ansi: env.komodo_logging_ansi.unwrap_or(config.logging.ansi),
timestamps: env
.komodo_logging_timestamps
.unwrap_or(config.logging.timestamps),
otlp_endpoint: env
.komodo_logging_otlp_endpoint
.unwrap_or(config.logging.otlp_endpoint),
@@ -433,13 +403,9 @@ pub fn core_config() -> &'static CoreConfig {
ssl_cert_file: env
.komodo_ssl_cert_file
.unwrap_or(config.ssl_cert_file),
reporting_enabled: env
.komodo_reporting_enabled
.unwrap_or(config.reporting_enabled),
reporting_private_key: env
.komodo_reporting_private_key
.unwrap_or(config.reporting_private_key),
ui_path: env.komodo_ui_path.unwrap_or(config.ui_path),
ui_path: env
.komodo_ui_path
.unwrap_or(config.ui_path),
ui_index_force_no_cache: env
.komodo_ui_index_force_no_cache
.unwrap_or(config.ui_index_force_no_cache),
@@ -456,7 +422,7 @@ pub fn core_config() -> &'static CoreConfig {
// These can't be overridden on env
secrets: config.secrets,
git_providers: config.git_providers,
image_registries: config.image_registries,
docker_registries: config.docker_registries,
}
})
}
-23
View File
@@ -1,7 +1,6 @@
use std::time::Duration;
use anyhow::{Context, anyhow};
use komodo_client::entities::server::Server;
use periphery_client::{
CONNECTION_RETRY_SECONDS, transport::LoginMessage,
};
@@ -19,7 +18,6 @@ use transport::{
use crate::{
config::{core_config, core_connection_query},
monitor::refresh_server_cache,
periphery::PeripheryClient,
state::periphery_connections,
};
@@ -48,7 +46,6 @@ impl PeripheryConnectionArgs<'_> {
let responses = connection.responses.clone();
let _id = id.clone();
tokio::spawn(async move {
loop {
let ws = tokio::select! {
@@ -73,14 +70,6 @@ impl PeripheryConnectionArgs<'_> {
}
};
debug!(
host = identifiers.host(),
query = core_connection_query(),
sec_websocket_accept = accept.to_str().unwrap_or_default(),
resource_id = connection.args.id,
"[PERIPHERY AUTH] Zero trust identifiers"
);
let identifiers = identifiers.build(
accept.as_bytes(),
core_connection_query().as_bytes(),
@@ -97,18 +86,6 @@ impl PeripheryConnectionArgs<'_> {
continue;
};
// Waits until after connection is handled then
// force refreshes the server cache.
let id = _id.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
let Ok(server) = crate::resource::get::<Server>(&id).await
else {
return;
};
refresh_server_cache(&server, true).await;
});
connection.handle_socket(socket, &mut receiver).await
}
});
+9 -30
View File
@@ -1,4 +1,4 @@
use std::{str::FromStr, time::Duration};
use std::str::FromStr;
use anyhow::{Context, anyhow};
use axum::{
@@ -42,7 +42,6 @@ use crate::{
api::write::WriteArgs,
config::core_keys,
helpers::query::id_or_name_filter,
monitor::refresh_server_cache,
resource::KomodoResource,
state::{db_client, periphery_connections},
};
@@ -130,14 +129,6 @@ async fn existing_server_handler(
return;
};
debug!(
host = identifiers.host.to_str().unwrap_or_default(),
query,
sec_websocket_accept = identifiers.accept,
resource_id = &server.id,
"[PERIPHERY AUTH] Zero trust identifiers"
);
let span = info_span!(
"PeripheryLogin",
server_id = server.id,
@@ -164,7 +155,7 @@ async fn existing_server_handler(
socket: &mut socket,
identifiers: identifiers.build(query.as_bytes()),
private_key: core_keys().load().private.as_str(),
public_key_validator: OnboardingKeyValidator { privileged_required: true },
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: true },
should_close: true
})
.await
@@ -202,13 +193,6 @@ async fn existing_server_handler(
return;
}
// Waits until after connection is handled then
// force refreshes the server cache.
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
refresh_server_cache(&server, true).await;
});
connection.handle_socket(socket, &mut receiver).await
}))
}
@@ -235,7 +219,7 @@ async fn fix_existing_server_handler(
socket: &mut socket,
identifiers: identifiers.build(query.as_bytes()),
private_key: core_keys().load().private.as_str(),
public_key_validator: OnboardingKeyValidator { privileged_required: true },
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: true },
should_close: true,
})
.await
@@ -307,13 +291,6 @@ async fn fix_server(
} else {
None
},
// Move address to external_address if not set.
// This helps preserve container port link behavior.
external_address: if !server.config.external_address.is_empty() {
Some(server.config.address)
} else {
None
},
..Default::default()
};
if !config.is_none() {
@@ -356,7 +333,7 @@ async fn onboard_new_server_handler(
socket: &mut socket,
identifiers: identifiers.build(query.as_bytes()),
private_key: core_keys().load().private.as_str(),
public_key_validator: OnboardingKeyValidator { privileged_required: false },
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: false },
should_close: true
})
.await
@@ -503,7 +480,7 @@ async fn create_server_maybe_builder(
name: server_query,
config: PartialBuilderConfig::Server(
PartialServerBuilderConfig {
server_ids: Some(vec![server.id.clone()]),
server_id: Some(server.id.clone()),
},
),
})
@@ -520,7 +497,7 @@ async fn create_server_maybe_builder(
}
struct OnboardingKeyValidator {
privileged_required: bool,
fix_existing_servers_required: bool,
}
impl PublicKeyValidator for OnboardingKeyValidator {
@@ -542,7 +519,9 @@ impl PublicKeyValidator for OnboardingKeyValidator {
{
return Err(anyhow!("Onboarding key is invalid"));
}
if self.privileged_required && !onboarding_key.privileged {
if self.fix_existing_servers_required
&& !onboarding_key.fix_existing_servers
{
return Err(anyhow!(
"Onboarding key not able to fix existing servers"
));
+27 -112
View File
@@ -1,18 +1,16 @@
use std::{collections::HashMap, time::Duration};
use std::time::Duration;
use anyhow::{Context, anyhow};
use database::mungos::mongodb::bson::oid::ObjectId;
use formatting::muted;
use futures_util::{StreamExt, stream::FuturesOrdered};
use komodo_client::entities::{
Version,
builder::{AwsBuilderConfig, Builder, BuilderConfig},
komodo_timestamp,
server::{Server, ServerState},
server::Server,
update::{Log, Update},
};
use periphery_client::api::{self, GetVersionResponse};
use tokio::sync::Mutex;
use crate::{
cloud::{
@@ -26,7 +24,6 @@ use crate::{
helpers::update::update_update,
periphery::PeripheryClient,
resource,
state::{builder_usage_cache, server_status_cache},
};
use super::periphery_client;
@@ -40,26 +37,25 @@ const BUILDER_POLL_MAX_TRIES: usize = 60;
fields(
resource_name,
builder_id = builder.id,
update_id = update.as_ref().map(|u| u.id.as_str())
update_id = update.id
)
)]
pub async fn connect_builder_periphery(
// build: &Build,
resource_name: String,
version: Option<Version>,
builder: Builder,
update: Option<&mut Update>,
update: &mut Update,
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
match builder.config {
BuilderConfig::Aws(config) => {
get_aws_builder(&resource_name, version, config, update).await
}
BuilderConfig::Url(config) => {
if config.address.is_empty() {
return Err(anyhow!(
"Builder has not yet configured an address"
));
}
// Builder id no good because it may be active for multiple connections.
// TODO: Dont use builder id, or will be problems
// with simultaneous spawned builders.
let periphery = PeripheryClient::new(
PeripheryConnectionArgs::from_url_builder(
&ObjectId::new().to_hex(),
@@ -68,66 +64,22 @@ pub async fn connect_builder_periphery(
config.insecure_tls,
)
.await?;
// Poll for connection to be estalished
let mut err = None;
for _ in 0..10 {
tokio::time::sleep(Duration::from_secs(1)).await;
match periphery
.health_check()
.await
.context("Url Builder failed health check")
{
Ok(_) => return Ok((periphery, BuildCleanupData::Url)),
Err(e) => err = Some(e),
};
}
Err(err.context("Missing error")?)
periphery
.health_check()
.await
.context("Url Builder failed health check")?;
Ok((periphery, BuildCleanupData::Url))
}
BuilderConfig::Server(config) => {
if config.server_ids.is_empty() {
return Err(anyhow!(
"Server Builder has no configured Servers"
));
if config.server_id.is_empty() {
return Err(anyhow!("Builder has not configured a server"));
}
// Short path for single configured builder
if config.server_ids.len() == 1 {
let server =
resource::get::<Server>(&config.server_ids[0]).await?;
let periphery = periphery_client(&server).await?;
return Ok((periphery, BuildCleanupData::Server(None)));
}
// Get filtered list of available Servers
let server_status_cache = server_status_cache();
let available_server_ids = config
.server_ids
.iter()
.map(|server_id| async move {
server_status_cache
.get(server_id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
.then_some(server_id)
})
.collect::<FuturesOrdered<_>>()
.collect::<Vec<_>>()
.await
.into_iter()
.flatten()
.collect::<Vec<_>>();
let selected = builder_usage_cache()
.get_or_insert_default(&builder.id)
.await
.select(&available_server_ids)
.await
.context("Server builder has no available servers")?;
let server = resource::get::<Server>(selected).await?;
let server = resource::get::<Server>(&config.server_id).await?;
let periphery = periphery_client(&server).await?;
Ok((periphery, BuildCleanupData::Server(Some(server.id))))
Ok((periphery, BuildCleanupData::Server))
}
BuilderConfig::Aws(config) => {
get_aws_builder(&resource_name, version, config, update).await
}
}
}
@@ -137,14 +89,14 @@ pub async fn connect_builder_periphery(
skip_all,
fields(
resource_name,
update_id = update.as_ref().map(|u| u.id.as_str()),
update_id = update.id,
)
)]
async fn get_aws_builder(
resource_name: &str,
version: Option<Version>,
config: AwsBuilderConfig,
mut update: Option<&mut Update>,
update: &mut Update,
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
let start_create_ts = komodo_timestamp();
@@ -162,10 +114,9 @@ async fn get_aws_builder(
..Default::default()
};
if let Some(update) = &mut update {
update.logs.push(log);
update_update((*update).clone()).await?;
}
update.logs.push(log);
update_update(update.clone()).await?;
let protocol = if config.use_https { "wss" } else { "ws" };
@@ -202,10 +153,8 @@ async fn get_aws_builder(
end_ts: komodo_timestamp(),
..Default::default()
};
if let Some(update) = update {
update.logs.push(connect_log);
update_update(update.clone()).await?;
}
update.logs.push(connect_log);
update_update(update.clone()).await?;
return Ok((
periphery,
BuildCleanupData::Aws {
@@ -245,17 +194,9 @@ pub async fn cleanup_builder_instance(
update: &mut Update,
) {
match cleanup_data {
BuildCleanupData::Server(None) => {
BuildCleanupData::Server => {
// Nothing to clean up
}
BuildCleanupData::Server(Some(builder_id)) => {
// Release periphery (server) id from builder
builder_usage_cache()
.get_or_insert_default(&builder_id)
.await
.release(&periphery.id)
.await
}
BuildCleanupData::Url => {
periphery.cleanup().await;
}
@@ -311,29 +252,3 @@ pub fn start_aws_builder_log(
]
.join("\n")
}
#[derive(Default)]
pub struct BuilderUsage(Mutex<HashMap<String, usize>>);
impl BuilderUsage {
pub async fn select<'a>(
&self,
available: &'a [&String],
) -> Option<&'a str> {
let mut lock = self.0.lock().await;
let selected = *available.iter().min_by_key(|key| {
lock.get(key.as_str()).copied().unwrap_or(0)
})?;
*lock.entry(selected.clone()).or_insert(0) += 1;
Some(selected.as_str())
}
pub async fn release(&self, key: &str) {
let mut lock = self.0.lock().await;
if let Some(count) = lock.get_mut(key)
&& *count > 0
{
*count -= 1;
}
}
}
+23 -23
View File
@@ -39,34 +39,34 @@ impl ImageDigestCache {
account: Option<String>,
token: Option<String>,
) -> anyhow::Result<ImageDigest> {
if let Some((digest, valid_until)) = self.0.get(image).await
if let Some((digest, valid_until)) = self.0.get(&image).await
// Ensure the query time was within last 10 mins to use cache.
&& valid_until > komodo_timestamp()
{
return Ok(digest);
}
let digest = swarm_or_server_request(
swarm_or_server,
GetLatestImageDigest {
name: image.clone(),
account,
token,
},
)
.await?
.digest;
let digest = ImageDigest::new(image, &digest);
self
.0
.insert(
image,
(digest.clone(), komodo_timestamp() + 10 * 60 * 1_000),
} else {
let digest = swarm_or_server_request(
&swarm_or_server,
GetLatestImageDigest {
name: image.clone(),
account,
token,
},
)
.await;
.await?
.digest;
Ok(digest)
let digest = ImageDigest::new(image, &digest);
self
.0
.insert(
image,
(digest.clone(), komodo_timestamp() + 10 * 60 * 1_000),
)
.await;
Ok(digest)
}
}
}
+1 -1
View File
@@ -169,7 +169,7 @@ pub async fn registry_token(
}
Ok(
core_config()
.image_registries
.docker_registries
.iter()
.find(|provider| provider.domain == provider_domain)
.and_then(|provider| {
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -55,7 +55,7 @@ async fn prune_images() -> anyhow::Result<()> {
while let Some((res, server)) = futures.next().await {
if let Err(e) = res {
warn!(
"Failed to prune images on Server {} ({}) | {e:#}",
"failed to prune images on Server {} ({}) | {e:#}",
server.name, server.id
)
}
+1 -33
View File
@@ -45,7 +45,7 @@ use crate::{
state::{
action_state_cache, action_states, db_client,
deployment_status_cache, procedure_state_cache,
server_status_cache, stack_status_cache,
stack_status_cache,
},
};
@@ -97,38 +97,6 @@ pub async fn get_server_state(server: &Server) -> ServerState {
}
}
/// The Server state as computed for the server list items,
/// from the in memory status cache.
pub async fn get_cached_server_state(id: &String) -> ServerState {
server_status_cache()
.get(id)
.await
.map(|status| status.state)
.unwrap_or_default()
}
/// The Stack state as computed for the stack list items,
/// from the in memory action states / status cache.
pub async fn get_cached_stack_state(id: &String) -> StackState {
if action_states()
.stack
.get(id)
.await
.map(|s| s.get().map(|s| s.deploying))
.transpose()
.ok()
.flatten()
.unwrap_or_default()
{
return StackState::Deploying;
}
stack_status_cache()
.get(id)
.await
.map(|status| status.curr.state)
.unwrap_or_default()
}
pub async fn get_deployment_state(
id: &String,
) -> anyhow::Result<DeploymentState> {
+23 -1
View File
@@ -1,3 +1,5 @@
use std::time::Duration;
use anyhow::anyhow;
use komodo_client::entities::server::{Server, ServerState};
use mogh_resolver::HasResponse;
@@ -11,6 +13,23 @@ pub async fn swarm_request<T>(
server_ids: &[String],
request: T,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Clone + Serialize + HasResponse,
T::Response: DeserializeOwned,
{
swarm_request_custom_timeout(
server_ids,
request,
Duration::from_secs(10),
)
.await
}
pub async fn swarm_request_custom_timeout<T>(
server_ids: &[String],
request: T,
timeout: Duration,
) -> anyhow::Result<T::Response>
where
T: std::fmt::Debug + Clone + Serialize + HasResponse,
T::Response: DeserializeOwned,
@@ -35,7 +54,10 @@ where
else {
continue;
};
match periphery.request(request.clone()).await {
match periphery
.request_custom_timeout(request.clone(), timeout)
.await
{
Ok(res) => return Ok(res),
Err(e) => err = Some(e),
}
+11 -10
View File
@@ -6,7 +6,7 @@ use komodo_client::{
permission::PermissionLevel,
server::Server,
stack::Stack,
terminal::{ContainerTerminalMode, Terminal, TerminalTarget},
terminal::{ContainerTerminalMode, TerminalTarget},
user::User,
},
};
@@ -84,7 +84,7 @@ async fn setup_server_target_for_user(
if let Some(init) = init {
periphery
.request(api::terminal::CreateServerTerminal {
name: Some(terminal.clone()),
name: terminal.clone(),
command: init.command,
recreate: init.recreate,
})
@@ -131,7 +131,7 @@ async fn setup_container_target_for_user(
if let Some(init) = init {
create_container_terminal_inner(
CreateTerminal {
name: Some(terminal.clone()),
name: terminal.clone(),
target: target.clone(),
command: init.command,
mode: init.mode,
@@ -166,7 +166,7 @@ async fn setup_stack_service_target_for_user(
if let Some(init) = init {
create_container_terminal_inner(
CreateTerminal {
name: Some(terminal.clone()),
name: terminal.clone(),
target: target.clone(),
command: init.command,
mode: init.mode,
@@ -199,7 +199,7 @@ async fn setup_deployment_target_for_user(
if let Some(init) = init {
create_container_terminal_inner(
CreateTerminal {
name: Some(terminal.clone()),
name: terminal.clone(),
target: target.clone(),
command: init.command,
mode: init.mode,
@@ -241,7 +241,7 @@ pub async fn create_container_terminal_inner(
}: CreateTerminal,
periphery: &PeripheryClient,
container: String,
) -> anyhow::Result<Terminal> {
) -> anyhow::Result<()> {
match mode.unwrap_or_default() {
ContainerTerminalMode::Exec => periphery
.request(periphery_client::api::terminal::CreateContainerExecTerminal {
@@ -254,7 +254,7 @@ pub async fn create_container_terminal_inner(
.await
.context(
"Failed to create Container Exec Terminal on Periphery",
),
)?,
ContainerTerminalMode::Attach => periphery
.request(periphery_client::api::terminal::CreateContainerAttachTerminal {
name,
@@ -265,8 +265,9 @@ pub async fn create_container_terminal_inner(
.await
.context(
"Failed to create Container Attach Terminal on Periphery",
),
}
)?,
};
Ok(())
}
pub async fn get_stack_service_periphery_container(
@@ -332,7 +333,7 @@ pub async fn get_deployment_periphery_container(
let periphery = periphery_client(&server).await?;
let container = deployment.deployed_name().to_string();
let container = deployment.name.clone();
Ok((
TerminalTarget::Deployment {
+457 -146
View File
@@ -123,153 +123,464 @@ pub async fn init_execution_update(
request: &ExecuteRequest,
user: &User,
) -> anyhow::Result<Update> {
macro_rules! init_execution_match {
(
resource: [$(($Variant:ident, $ResType:ident, $field:ident)),* $(,)?],
batch: [$($BatchVariant:ident),* $(,)?],
stack_service: [$(($StackVariant:ident, $ServiceOp:ident)),* $(,)?],
system: [$($SysVariant:ident),* $(,)?],
) => {
match &request {
$(
ExecuteRequest::$Variant(data) => (
Operation::$Variant,
ResourceTarget::$ResType(
resource::get::<$ResType>(&data.$field).await?.id,
),
),
)*
$(
ExecuteRequest::$BatchVariant(_data) => {
return Ok(Default::default());
}
)*
$(
ExecuteRequest::$StackVariant(data) => (
if !data.services.is_empty() {
Operation::$ServiceOp
} else {
Operation::$StackVariant
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
)*
// DeployStackIfChanged doesn't have a service variant
ExecuteRequest::DeployStackIfChanged(data) => (
Operation::DeployStack,
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
$(
ExecuteRequest::$SysVariant(_data) => {
(Operation::$SysVariant, ResourceTarget::system())
}
)*
}
};
}
let (operation, target) = match &request {
// Swarm
ExecuteRequest::RemoveSwarmNodes(data) => (
Operation::RemoveSwarmNodes,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RemoveSwarmStacks(data) => (
Operation::RemoveSwarmStacks,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RemoveSwarmServices(data) => (
Operation::RemoveSwarmServices,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::CreateSwarmConfig(data) => (
Operation::CreateSwarmConfig,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RotateSwarmConfig(data) => (
Operation::RotateSwarmConfig,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RemoveSwarmConfigs(data) => (
Operation::RemoveSwarmConfigs,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::CreateSwarmSecret(data) => (
Operation::CreateSwarmSecret,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RotateSwarmSecret(data) => (
Operation::RotateSwarmSecret,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
ExecuteRequest::RemoveSwarmSecrets(data) => (
Operation::RemoveSwarmSecrets,
ResourceTarget::Swarm(
resource::get::<Swarm>(&data.swarm).await?.id,
),
),
let (operation, target) = init_execution_match!(
resource: [
// Swarm
(RemoveSwarmNodes, Swarm, swarm),
(UpdateSwarmNode, Swarm, swarm),
(RemoveSwarmStacks, Swarm, swarm),
(RemoveSwarmServices, Swarm, swarm),
(CreateSwarmConfig, Swarm, swarm),
(RotateSwarmConfig, Swarm, swarm),
(RemoveSwarmConfigs, Swarm, swarm),
(CreateSwarmSecret, Swarm, swarm),
(RotateSwarmSecret, Swarm, swarm),
(RemoveSwarmSecrets, Swarm, swarm),
// Server
(StartContainer, Server, server),
(RestartContainer, Server, server),
(PauseContainer, Server, server),
(UnpauseContainer, Server, server),
(StopContainer, Server, server),
(DestroyContainer, Server, server),
(StartAllContainers, Server, server),
(RestartAllContainers, Server, server),
(PauseAllContainers, Server, server),
(UnpauseAllContainers, Server, server),
(StopAllContainers, Server, server),
(PruneContainers, Server, server),
(DeleteNetwork, Server, server),
(PruneNetworks, Server, server),
(DeleteImage, Server, server),
(PruneImages, Server, server),
(DeleteVolume, Server, server),
(PruneVolumes, Server, server),
(PruneDockerBuilders, Server, server),
(PruneBuildx, Server, server),
(PruneSystem, Server, server),
// Deployment
(Deploy, Deployment, deployment),
(PullDeployment, Deployment, deployment),
(StartDeployment, Deployment, deployment),
(RestartDeployment, Deployment, deployment),
(PauseDeployment, Deployment, deployment),
(UnpauseDeployment, Deployment, deployment),
(StopDeployment, Deployment, deployment),
(DestroyDeployment, Deployment, deployment),
// Build
(RunBuild, Build, build),
(CancelBuild, Build, build),
// Repo
(CloneRepo, Repo, repo),
(PullRepo, Repo, repo),
(BuildRepo, Repo, repo),
(CancelRepoBuild, Repo, repo),
// Procedure
(RunProcedure, Procedure, procedure),
(CancelProcedure, Procedure, procedure),
// Action
(RunAction, Action, action),
(CancelAction, Action, action),
// Resource Sync
(RunSync, ResourceSync, sync),
// Stack (simple)
(RunStackService, Stack, stack),
// Alerter
(TestAlerter, Alerter, alerter),
],
batch: [
BatchDeploy,
BatchDestroyDeployment,
BatchRunBuild,
BatchCloneRepo,
BatchPullRepo,
BatchBuildRepo,
BatchRunProcedure,
BatchRunAction,
BatchDeployStack,
BatchDeployStackIfChanged,
BatchPullStack,
BatchDestroyStack,
],
stack_service: [
(DeployStack, DeployStackService),
(PullStack, PullStackService),
(StartStack, StartStackService),
(RestartStack, RestartStackService),
(PauseStack, PauseStackService),
(UnpauseStack, UnpauseStackService),
(StopStack, StopStackService),
(DestroyStack, DestroyStackService),
],
system: [
SendAlert,
ClearRepoCache,
BackupCoreDatabase,
GlobalAutoUpdate,
RotateAllServerKeys,
RotateCoreKeys,
],
);
// Server
ExecuteRequest::StartContainer(data) => (
Operation::StartContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::RestartContainer(data) => (
Operation::RestartContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PauseContainer(data) => (
Operation::PauseContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::UnpauseContainer(data) => (
Operation::UnpauseContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::StopContainer(data) => (
Operation::StopContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::DestroyContainer(data) => (
Operation::DestroyContainer,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::StartAllContainers(data) => (
Operation::StartAllContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::RestartAllContainers(data) => (
Operation::RestartAllContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PauseAllContainers(data) => (
Operation::PauseAllContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::UnpauseAllContainers(data) => (
Operation::UnpauseAllContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::StopAllContainers(data) => (
Operation::StopAllContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneContainers(data) => (
Operation::PruneContainers,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::DeleteNetwork(data) => (
Operation::DeleteNetwork,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneNetworks(data) => (
Operation::PruneNetworks,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::DeleteImage(data) => (
Operation::DeleteImage,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneImages(data) => (
Operation::PruneImages,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::DeleteVolume(data) => (
Operation::DeleteVolume,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneVolumes(data) => (
Operation::PruneVolumes,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneDockerBuilders(data) => (
Operation::PruneDockerBuilders,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneBuildx(data) => (
Operation::PruneBuildx,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
ExecuteRequest::PruneSystem(data) => (
Operation::PruneSystem,
ResourceTarget::Server(
resource::get::<Server>(&data.server).await?.id,
),
),
// Deployment
ExecuteRequest::Deploy(data) => (
Operation::Deploy,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::BatchDeploy(_data) => {
return Ok(Default::default());
}
ExecuteRequest::PullDeployment(data) => (
Operation::PullDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::StartDeployment(data) => (
Operation::StartDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::RestartDeployment(data) => (
Operation::RestartDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::PauseDeployment(data) => (
Operation::PauseDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::UnpauseDeployment(data) => (
Operation::UnpauseDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::StopDeployment(data) => (
Operation::StopDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::DestroyDeployment(data) => (
Operation::DestroyDeployment,
ResourceTarget::Deployment(
resource::get::<Deployment>(&data.deployment).await?.id,
),
),
ExecuteRequest::BatchDestroyDeployment(_data) => {
return Ok(Default::default());
}
// Build
ExecuteRequest::RunBuild(data) => (
Operation::RunBuild,
ResourceTarget::Build(
resource::get::<Build>(&data.build).await?.id,
),
),
ExecuteRequest::BatchRunBuild(_data) => {
return Ok(Default::default());
}
ExecuteRequest::CancelBuild(data) => (
Operation::CancelBuild,
ResourceTarget::Build(
resource::get::<Build>(&data.build).await?.id,
),
),
// Repo
ExecuteRequest::CloneRepo(data) => (
Operation::CloneRepo,
ResourceTarget::Repo(
resource::get::<Repo>(&data.repo).await?.id,
),
),
ExecuteRequest::BatchCloneRepo(_data) => {
return Ok(Default::default());
}
ExecuteRequest::PullRepo(data) => (
Operation::PullRepo,
ResourceTarget::Repo(
resource::get::<Repo>(&data.repo).await?.id,
),
),
ExecuteRequest::BatchPullRepo(_data) => {
return Ok(Default::default());
}
ExecuteRequest::BuildRepo(data) => (
Operation::BuildRepo,
ResourceTarget::Repo(
resource::get::<Repo>(&data.repo).await?.id,
),
),
ExecuteRequest::BatchBuildRepo(_data) => {
return Ok(Default::default());
}
ExecuteRequest::CancelRepoBuild(data) => (
Operation::CancelRepoBuild,
ResourceTarget::Repo(
resource::get::<Repo>(&data.repo).await?.id,
),
),
// Procedure
ExecuteRequest::RunProcedure(data) => (
Operation::RunProcedure,
ResourceTarget::Procedure(
resource::get::<Procedure>(&data.procedure).await?.id,
),
),
ExecuteRequest::BatchRunProcedure(_) => {
return Ok(Default::default());
}
// Action
ExecuteRequest::RunAction(data) => (
Operation::RunAction,
ResourceTarget::Action(
resource::get::<Action>(&data.action).await?.id,
),
),
ExecuteRequest::BatchRunAction(_) => {
return Ok(Default::default());
}
// Resource Sync
ExecuteRequest::RunSync(data) => (
Operation::RunSync,
ResourceTarget::ResourceSync(
resource::get::<ResourceSync>(&data.sync).await?.id,
),
),
// Stack
ExecuteRequest::DeployStack(data) => (
if !data.services.is_empty() {
Operation::DeployStackService
} else {
Operation::DeployStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::BatchDeployStack(_data) => {
return Ok(Default::default());
}
ExecuteRequest::DeployStackIfChanged(data) => (
Operation::DeployStack,
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::BatchDeployStackIfChanged(_data) => {
return Ok(Default::default());
}
ExecuteRequest::StartStack(data) => (
if !data.services.is_empty() {
Operation::StartStackService
} else {
Operation::StartStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::PullStack(data) => (
if !data.services.is_empty() {
Operation::PullStackService
} else {
Operation::PullStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::BatchPullStack(_data) => {
return Ok(Default::default());
}
ExecuteRequest::RestartStack(data) => (
if !data.services.is_empty() {
Operation::RestartStackService
} else {
Operation::RestartStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::PauseStack(data) => (
if !data.services.is_empty() {
Operation::PauseStackService
} else {
Operation::PauseStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::UnpauseStack(data) => (
if !data.services.is_empty() {
Operation::UnpauseStackService
} else {
Operation::UnpauseStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::StopStack(data) => (
if !data.services.is_empty() {
Operation::StopStackService
} else {
Operation::StopStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::DestroyStack(data) => (
if !data.services.is_empty() {
Operation::DestroyStackService
} else {
Operation::DestroyStack
},
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
ExecuteRequest::BatchDestroyStack(_data) => {
return Ok(Default::default());
}
ExecuteRequest::RunStackService(data) => (
Operation::RunStackService,
ResourceTarget::Stack(
resource::get::<Stack>(&data.stack).await?.id,
),
),
// Alerter
ExecuteRequest::TestAlerter(data) => (
Operation::TestAlerter,
ResourceTarget::Alerter(
resource::get::<Alerter>(&data.alerter).await?.id,
),
),
ExecuteRequest::SendAlert(_) => {
(Operation::SendAlert, ResourceTarget::system())
}
// Maintenance
ExecuteRequest::ClearRepoCache(_data) => {
(Operation::ClearRepoCache, ResourceTarget::system())
}
ExecuteRequest::BackupCoreDatabase(_data) => {
(Operation::BackupCoreDatabase, ResourceTarget::system())
}
ExecuteRequest::GlobalAutoUpdate(_data) => {
(Operation::GlobalAutoUpdate, ResourceTarget::system())
}
ExecuteRequest::RotateAllServerKeys(_data) => {
(Operation::RotateAllServerKeys, ResourceTarget::system())
}
ExecuteRequest::RotateCoreKeys(_data) => {
(Operation::RotateCoreKeys, ResourceTarget::system())
}
};
let mut update = make_update(target, operation, user);
update.in_progress();
-2
View File
@@ -19,7 +19,6 @@ mod monitor;
mod network;
mod periphery;
mod permission;
mod report;
mod resource;
mod schedule;
mod stack;
@@ -72,7 +71,6 @@ async fn app() -> anyhow::Result<()> {
resource::spawn_action_state_refresh_loop();
schedule::spawn_schedule_executor();
helpers::prune::spawn_prune_loop();
report::spawn_reporting_loop();
}
.instrument(startup_span)
.await;
+1 -2
View File
@@ -3,8 +3,7 @@ use std::collections::HashMap;
use komodo_client::entities::{
ResourceTarget,
alert::{Alert, AlertData, SeverityLevel},
deployment::{Deployment, DeploymentState},
optional_string,
deployment::{Deployment, DeploymentState}, optional_string,
};
use crate::{
+1 -7
View File
@@ -2,9 +2,7 @@ use std::{collections::HashMap, sync::Mutex};
use anyhow::Context;
use komodo_client::entities::{
alert::AlertDataVariant, permission::PermissionLevel,
resource::ResourceQuery, server::Server, swarm::Swarm,
user::system_user,
alert::AlertDataVariant, permission::PermissionLevel, resource::ResourceQuery, server::Server, swarm::Swarm, user::system_user
};
use crate::resource;
@@ -36,8 +34,6 @@ async fn get_all_swarms_map()
-> anyhow::Result<(HashMap<String, Swarm>, HashMap<String, String>)> {
let swarms = resource::list_full_for_user::<Swarm>(
ResourceQuery::default(),
None,
None,
system_user(),
PermissionLevel::Read.into(),
&[],
@@ -63,8 +59,6 @@ async fn get_all_servers_map()
{
let servers = resource::list_full_for_user::<Server>(
ResourceQuery::default(),
None,
None,
system_user(),
PermissionLevel::Read.into(),
&[],
+62 -162
View File
@@ -51,7 +51,7 @@ pub async fn alert_servers(
let mut alerts_to_open = Vec::<(Alert, SendAlerts)>::new();
let mut alerts_to_update = Vec::<(Alert, SendAlerts)>::new();
let mut alerts_to_close = Vec::<(Alert, SendAlerts)>::new();
let mut alert_ids_to_close = Vec::<(Alert, SendAlerts)>::new();
let buffer = alert_buffer();
@@ -66,21 +66,15 @@ pub async fn alert_servers(
let in_maintenance =
is_in_maintenance(&server.config.maintenance_windows, ts);
// ===============
// SERVER HEALTH
// ===============
// ===================
// SERVER HEALTH
// ===================
let health_alert = server_alerts.as_ref().and_then(|alerts| {
alerts.get(&AlertDataVariant::ServerUnreachable)
});
match (
server_status.state,
health_alert,
server.config.send_unreachable_alerts,
) {
// Skip alerting if alerts disabled
(ServerState::NotOk, None, false) => {}
(ServerState::NotOk, None, true) => {
// Only open unreachable alert if unreachable alerts enabled and not in maintenance and buffer is ready
match (server_status.state, health_alert) {
(ServerState::NotOk, None) => {
// Only open unreachable alert if not in maintenance and buffer is ready
if !in_maintenance
&& buffer.ready_to_open(
server_status.id.clone(),
@@ -105,14 +99,19 @@ pub async fn alert_servers(
.push((alert, server.config.send_unreachable_alerts))
}
}
(ServerState::NotOk, Some(alert), true) => {
(ServerState::NotOk, Some(alert)) => {
// update alert err
let mut alert = alert.clone();
let (id, name, region) = match alert.data {
AlertData::ServerUnreachable {
id, name, region, ..
} => (id, name, region),
_ => unreachable!(),
data => {
error!(
"got incorrect alert data in ServerStatus handler. got {data:?}"
);
continue;
}
};
alert.data = AlertData::ServerUnreachable {
id,
@@ -125,30 +124,23 @@ pub async fn alert_servers(
alerts_to_update.push((alert, false));
}
(ServerState::NotOk, Some(alert), false) => {
alerts_to_close.push((
alert.clone(),
server.config.send_unreachable_alerts,
));
}
// Close an open alert
(ServerState::Ok | ServerState::Disabled, Some(alert), _) => {
alerts_to_close.push((
(ServerState::Ok | ServerState::Disabled, Some(alert)) => {
alert_ids_to_close.push((
alert.clone(),
server.config.send_unreachable_alerts,
));
}
(ServerState::Ok | ServerState::Disabled, None, _) => buffer
(ServerState::Ok | ServerState::Disabled, None) => buffer
.reset(
server_status.id.clone(),
AlertDataVariant::ServerUnreachable,
),
}
// ========================
// ===================
// SERVER VERSION MISMATCH
// ========================
// ===================
let core_version = env!("CARGO_PKG_VERSION");
let mismatched_server_version =
if server_status.state != ServerState::Ok {
@@ -166,14 +158,9 @@ pub async fn alert_servers(
alerts.get(&AlertDataVariant::ServerVersionMismatch)
});
match (
mismatched_server_version,
version_alert,
server.config.send_version_mismatch_alerts,
) {
(Some(_), None, false) => {}
(Some(version), None, true) => {
// Only open version mismatch alert if version mismatch alerts enabled and not in maintenance and buffer is ready
match (mismatched_server_version, version_alert) {
(Some(version), None) => {
// Only open version mismatch alert if not in maintenance and buffer is ready
if !in_maintenance
&& buffer.ready_to_open(
server_status.id.clone(),
@@ -200,47 +187,27 @@ pub async fn alert_servers(
.push((alert, server.config.send_version_mismatch_alerts))
}
}
(Some(version), Some(alert), true) => {
// Update existing alert with current version info if changed
let (alert_server_version, alert_core_version) =
match &alert.data {
AlertData::ServerVersionMismatch {
server_version,
core_version,
..
} => (server_version, core_version),
_ => unreachable!(),
};
if alert_server_version != version
|| alert_core_version != core_version
{
let mut alert = alert.clone();
alert.data = AlertData::ServerVersionMismatch {
id: server_status.id.clone(),
name: server.name.clone(),
region: optional_string(&server.config.region),
server_version: version.clone(),
core_version: core_version.to_string(),
};
// Don't send notification for updates
alerts_to_update.push((alert, false));
}
(Some(version), Some(alert)) => {
// Update existing alert with current version info
let mut alert = alert.clone();
alert.data = AlertData::ServerVersionMismatch {
id: server_status.id.clone(),
name: server.name.clone(),
region: optional_string(&server.config.region),
server_version: version.clone(),
core_version: core_version.to_string(),
};
// Don't send notification for updates
alerts_to_update.push((alert, false));
}
(Some(_), Some(alert), false) => {
alerts_to_close.push((
alert.clone(),
server.config.send_version_mismatch_alerts,
));
}
(None, Some(alert), _) => {
(None, Some(alert)) => {
// Version is now correct, close the alert
alerts_to_close.push((
alert_ids_to_close.push((
alert.clone(),
server.config.send_version_mismatch_alerts,
));
}
(None, None, _) => {
(None, None) => {
// Reset buffer state when no mismatch and no alert
buffer.reset(
server_status.id.clone(),
@@ -260,25 +227,10 @@ pub async fn alert_servers(
.as_ref()
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerCpu))
.cloned();
match (
health.cpu.level,
cpu_alert,
health.cpu.should_close_alert,
server.config.send_cpu_alerts,
) {
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
false,
) => {}
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
true,
) => {
// Only open CPU alert if cpu alerts enabled and not in maintenance and buffer is ready
match (health.cpu.level, cpu_alert, health.cpu.should_close_alert)
{
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
// Only open CPU alert if not in maintenance and buffer is ready
if !in_maintenance
&& buffer.ready_to_open(
server_status.id.clone(),
@@ -310,7 +262,6 @@ pub async fn alert_servers(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(mut alert),
_,
true,
) => {
// modify alert level only if it has increased and not in maintenance
if !in_maintenance && alert.level < health.cpu.level {
@@ -329,15 +280,8 @@ pub async fn alert_servers(
.push((alert, server.config.send_cpu_alerts));
}
}
(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(alert),
_,
false,
) => {
alerts_to_close.push((alert, server.config.send_cpu_alerts));
}
(SeverityLevel::Ok, Some(mut alert), true, _) => {
(SeverityLevel::Ok, Some(alert), true) => {
let mut alert = alert.clone();
alert.data = AlertData::ServerCpu {
id: server_status.id.clone(),
name: server.name.clone(),
@@ -348,9 +292,10 @@ pub async fn alert_servers(
.map(|s| s.cpu_perc as f64)
.unwrap_or(0.0),
};
alerts_to_close.push((alert, server.config.send_cpu_alerts))
alert_ids_to_close
.push((alert, server.config.send_cpu_alerts))
}
(SeverityLevel::Ok, _, _, _) => buffer
(SeverityLevel::Ok, _, _) => buffer
.reset(server_status.id.clone(), AlertDataVariant::ServerCpu),
}
@@ -361,25 +306,10 @@ pub async fn alert_servers(
.as_ref()
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerMem))
.cloned();
match (
health.mem.level,
mem_alert,
health.mem.should_close_alert,
server.config.send_mem_alerts,
) {
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
false,
) => {}
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
true,
) => {
// Only open memory alert if memory alerts enabled and not in maintenance and buffer is ready
match (health.mem.level, mem_alert, health.mem.should_close_alert)
{
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
// Only open memory alert if not in maintenance and buffer is ready
if !in_maintenance
&& buffer.ready_to_open(
server_status.id.clone(),
@@ -416,7 +346,6 @@ pub async fn alert_servers(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(mut alert),
_,
true,
) => {
// modify alert level only if it has increased and not in maintenance
if !in_maintenance && alert.level < health.mem.level {
@@ -440,16 +369,7 @@ pub async fn alert_servers(
.push((alert, server.config.send_mem_alerts));
}
}
(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(alert),
_,
false,
) => {
alerts_to_close
.push((alert.clone(), server.config.send_mem_alerts));
}
(SeverityLevel::Ok, Some(alert), true, _) => {
(SeverityLevel::Ok, Some(alert), true) => {
let mut alert = alert.clone();
alert.data = AlertData::ServerMem {
id: server_status.id.clone(),
@@ -466,9 +386,10 @@ pub async fn alert_servers(
.map(|s| s.mem_used_gb)
.unwrap_or(0.0),
};
alerts_to_close.push((alert, server.config.send_mem_alerts))
alert_ids_to_close
.push((alert, server.config.send_mem_alerts))
}
(SeverityLevel::Ok, _, _, _) => buffer
(SeverityLevel::Ok, _, _) => buffer
.reset(server_status.id.clone(), AlertDataVariant::ServerMem),
}
@@ -484,25 +405,13 @@ pub async fn alert_servers(
.as_ref()
.and_then(|alerts| alerts.get(path))
.cloned();
match (
health.level,
disk_alert,
health.should_close_alert,
server.config.send_disk_alerts,
) {
match (health.level, disk_alert, health.should_close_alert) {
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
false,
) => {}
(
SeverityLevel::Warning | SeverityLevel::Critical,
None,
_,
true,
) => {
// Only open disk alert if disk alerts enabled and not in maintenance and buffer is ready
// Only open disk alert if not in maintenance and buffer is ready
if !in_maintenance
&& buffer.ready_to_open(
server_status.id.clone(),
@@ -541,7 +450,6 @@ pub async fn alert_servers(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(mut alert),
_,
true,
) => {
// modify alert level only if it has increased and not in maintenance
if !in_maintenance && health.level < alert.level {
@@ -562,16 +470,8 @@ pub async fn alert_servers(
.push((alert, server.config.send_disk_alerts));
}
}
(
SeverityLevel::Warning | SeverityLevel::Critical,
Some(alert),
_,
false,
) => {
alerts_to_close
.push((alert, server.config.send_disk_alerts));
}
(SeverityLevel::Ok, Some(mut alert), true, _) => {
(SeverityLevel::Ok, Some(alert), true) => {
let mut alert = alert.clone();
let disk =
server_status.system_stats.as_ref().and_then(|stats| {
stats.disks.iter().find(|disk| disk.mount == *path)
@@ -585,10 +485,10 @@ pub async fn alert_servers(
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
};
alerts_to_close
alert_ids_to_close
.push((alert, server.config.send_disk_alerts))
}
(SeverityLevel::Ok, _, _, _) => buffer.reset(
(SeverityLevel::Ok, _, _) => buffer.reset(
server_status.id.clone(),
AlertDataVariant::ServerDisk,
),
@@ -601,7 +501,7 @@ pub async fn alert_servers(
if !health.disks.contains_key(path) {
let mut alert = alert.clone();
alert.level = SeverityLevel::Ok;
alerts_to_close
alert_ids_to_close
.push((alert, server.config.send_disk_alerts));
}
}
@@ -611,7 +511,7 @@ pub async fn alert_servers(
tokio::join!(
open_new_alerts(&alerts_to_open),
update_alerts(&alerts_to_update),
resolve_alerts(&alerts_to_close),
resolve_alerts(&alert_ids_to_close),
);
}

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