Compare commits

..
16 Commits
Author SHA1 Message Date
mbecker20 254622078e rust 1.94.1 2026-04-01 16:11:27 -07:00
38421d6719 2.1.0 (#1285)
* fix swarm config content hidden

* 2.0.1-dev-1

* bump ts deps

* align swarm config links

* StackServiceRun command is actually optional

* fix swarm not included in read resource toml

* deploy 2.0.1-dev-2

* logger support disabling timestamps (eg if they are already provided by docker logs)

* full opacity tag color selector

* Add UpdateSwarmNode execution and convert several large repetive blocks to macro handling

* add node update functionality

* deploy 2.0.1-dev-3

* deploy 2.1.0-dev-1

* toml variable export sorted by name

* deploy 2.1.0-dev-2

* dont use detach=false because it can hang indefinitely on misconfigured stacks

* deploy 2.1.0-dev-3

* swarm stack deploy explicitly '--detach=true' to avoid future changes in behavior

* fmt

* UI: Stack config: Fix Add Env File button

* fix swarm error propogation, add server connection error hover message

* deploy 2.1.0-dev-4

* fix service rm (should be stack rm

* fix container ports not displaying when server address not available for link

* fix build registry custom org configuration

* github action: comment out cache step (leads to no space on device)

* fix missing nullish check when selecting stack (#1287)

* fix container selector null crash

* provider selector custom input label adheres to showLabel

* fix: show/hide button non-functional in stack, sync, and build info views (#1267)

Co-authored-by: twalts <t.mwalton@yahoo.com>

* swarm stack support env vars through shell source file method

* tweaks

* bump deps

* deploy 2.1.0-dev-5

* auto update should work with swarm

* deploy 2.1.0-dev-6

* 2.1.0

---------

Co-authored-by: Shen Li <dustet@gmail.com>
Co-authored-by: T <github@mail.taylor.media>
Co-authored-by: twalts <t.mwalton@yahoo.com>
2026-04-01 16:08:29 -07:00
mbecker20 0f96794559 add ghpages CNAME 2026-03-26 13:05:06 -07:00
mbecker20 d79fefff17 add breaking notice about bullseye and periphery compose example with correct key path 2026-03-26 13:02:39 -07:00
mbecker20 4626b4a477 fix deep compare short path for falsy / null 2026-03-25 02:27:46 -07:00
mbecker20 150a8a160d fix terminals page new terminals menu 2026-03-25 01:56:20 -07:00
mbecker20 5b3bb9b8bb Use :2 to stay up to date
with all future Komodo v2 releases.
2026-03-24 18:40:19 -07:00
mbecker20 79831f29af Improved auto updates 2026-03-24 18:38:03 -07:00
mbecker20 b68f5abd25 fix: max width on git commit messages 2026-03-24 17:46:00 -07:00
mbecker20 2325899ebf quick fix swarm config (UI only) 2026-03-24 17:42:09 -07:00
mbecker20 23fd208f0f cli docker alias need :2 in the image 2026-03-24 16:53:45 -07:00
mbecker20 19904e00b9 docs on terminals 2026-03-24 16:48:58 -07:00
mbecker20 8880b1c948 km exec finds server name for connection log 2026-03-24 16:45:33 -07:00
mbecker20 e47c8d2b62 docsite fix privileged typo 2026-03-24 16:17:31 -07:00
mbecker20 1addb8fccd update roadmap with swarm support 2026-03-24 07:04:25 -07:00
08e55b5180 2.0.0 (#889)
* modularize openapi structs

* most of execute openapi

* server / stack exec openapi

* most of write openapi

* more write openapi

* add the write openapi definitions

* fmt and bump mogh auth

* gen types

* remove user api, mogh auth handles api keys

* clean up

* cache latest image digests and use this for image update alerting / auto update

* deploy 2.0.0-dev-108

* deploy 2.0.0-dev-109

* add back legacy Action "exec" method calling convention

* typegen

* improve config quick links styling

* improve config styling a bit more

* deploy 2.0.0-dev-110

* finish version upgrades page, and use prism theme oneLight and oneDark

* stack level terminal page

* fix Action import from terminal

* clean up dangling action api keys on startup

* improve swarm service / stack state inference

* deploy 2.0.0-dev-111

* add api docs link

* bump ts types and fix SwarmConfig type name conflict

* use mogh auth for passkey conversion

* align dashboard icon with sidebar

* bump frontend node version

* bump node version in all the dockerfiles

* fix tailwind config module

* fix require to import

* skip auto update for images with pinned digest

* dev 112

* cleanup

* batch check for updates, add check to table multi select actions

* dev-113

* mogh_server = "1.2.0"

* serve static ui index.html with ETag / no-cache

* check update available against all digests for image

* deploy 2.0.0-dev-114

* bump rust to 1.93.0

* bump mogh server and auth

* configure session allow cross site

* deploy 2.0.0-dev-115

* add new config value to example config

* Stack check for update prefers check against deployed service image

* deploy 2.0.0-dev-116

* only send auto updated alert after verifying the deploy was successful

* 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

* bump rust version to 1.93.1 and dep versions

* deploy 2.0.0-dev-118

* bump chef rust version

* improve login no auth configured and passkey pending

* Load Average is first historical stat

* procedure / action webhook branch mobile style

* dashboard active styling

* hide actions when none

* Select Template

* execution buttons disabled when loading

* Fix config input issue

* improve tab styling

* rename ConfigSwitch onChange -> onCheckedChange

* ensure section headers consistent spacing

* edit swarm join command

* fix batch executions width

* stack stopped and deployment exited warning instead of critical

* smaller more consistent gaps

* add close button to update / alert details drawer

* stack and deployment state color include update available. Ensure server version mismatch color applied everywhere

* deploy 2.0.0-dev-120

* topbar user dropdown shows user avatar if available

* post link redirect should be to profile

* deploy 2.0.0-dev-121

* improve profile delete styling

* standard api key modal size

* improve login styling

* fix login github / google icon color

* fix some wrapping stuff in tables and tag text disappear

* fix terminal height - same as logs

* single delete terminal

* Update / Alert table filter selector formatted

* tweaks for lg size

* taller data table and blue omnisearch

* refine lg screen size view

* fix sidebar margin right

* "never" -> "Never"

* Add hoverable disk info

* improve disk usage hover card styling

* rename for clarity

* thinner topbar

* config sidebar save

* setters use maps instead of mutations

* notification green contents written success

* fmt

* read request are trace

* deploy 2.0.0-dev-122

* debug level core <> periphery auth identifiers logs

* mogh auth server 1.2.10

* mogh auth 1.2.11

* deploy 2.0.0-dev-123

* Deploy / DeployStack updates invalidate corresponding list query

* confirm action / save modals don't need ConfirmButton

* deploy 2.0.0-dev-124

* proper base64url decode

* fix init admin user

* improve mobile friendly tabs width, and onboardng key copy

* rename resource invalidates

* better maxheight for mobile friendly tabs

* km cli needs to install crypto provider for tls ws

* better responsive confirm save width

* confirm modal better responsiveness

* Fix api key modal too thin

* better api key create

* improve batch execs

* improve tabs

* sidebar more compact

* add missing Repo header Info

* Fix Pull repo

* fix repo Links config

* improve procedure config UI including run stack service

* improve deployment network, restart, termination signal config

* fix confirm update showing entries which have not changed

* example execute terminal uses bash

* Update deployments description

* move build server

* [Docs] Update connect-servers.mdx (#1256)

* Update connect-servers.mdx

* Update connect-servers.mdx

* clean up connect servers

* feat(ci): build (#1018)

* fmt

* fix: more verbose logging (#1017)

* use .with_context for stack run directory canonicalize log

* fix failing doc test

* Improve server resource header hover info

* service selector support swarm stack icon

* fix stack terminals sometimes not disabled when it should be

* add terminal create and delete messages

* bump packages and add Mogh Tech copyright

* revamp docsite

* soften the borders

* clean up stack config

* fix config group header too much gap

* procedure stage menu easier to reach

* deploy 2.0.0-dev-125

* increase universal resource polling

* improve server stats

* fix data table

* down node is critical

* cli add print core info

* add docker swarm feature card

* improve toml resource repetition using macros, and fix Swarm toml support

* swarm config: configure alerting options

* add swarm header info

* Implement New Swarm Config and New Swarm Secret

* brighten tag colors

* continue docs revamp

* fmt

* set more refetch intervals to keep display data fresh

* fix copy not showing copy source when there are no templates

* rename onboarding key fix_existing_servers to privileged

* fix Privileged spelling

* fmt

* more docs improvement

* improve docs intro

* update the curl instructions with easier call method

* fix clippy lints

* refresh the server cache after every server connection successful login

* deploy 2.0.0-dev-126

* add polling to dashboard summary data

* tweak tag opacity

* improve server stat table disk hover

* fix change historical stat length collapse stats

* KOMODO_DISABLE_INIT_RESOURCES

* stack Project Missing should be red

* Fix files on host stacks showing down after reboot until refresh cache

* silence user level write logs SetLastSeenUpdate and PushRecentlyViewed

* See the v2 migration guide

* Improve api logging using more fields

* deploy 2.0.0-dev-127

* cli create api key on database, can use with docker exec into core container

* deploy 2.0.0-dev-128

* advanced km create api-key options

* create onboarding key with cli

* tweak

* onboarding keys use tag name

* deploy 2.0.0-dev-129

* stack procedure stages can select specific services to apply to (default all services)

* docsite dockerfile

* ./docusaurus.config.ts

* need to yarn build

* fix broken link

* improve alert (dropdown) icons

* fix docs sidebar collapse

* add local search functionality

* docs search nice

* homepage features navigate to docs

* only show build cancel when canCancel

* more confirm button confirmprops red

* fix docsite buttons and list more features

* fix sidebar cmd click opens in new tab (is a link)

* execute withBorder

* cleaner execute section

* swap docker compose and deploy containers

* make docs logo align with app

* better mobile button layout

* linked logins / 2fa confirm red styling

* bump rust version to 1.94.0

* move bollard::secret to bollard::config

* deploy 2.0.0-dev-130

* feat: add compose_cmd_wrapper_include for selective command wrapping (#1124)

- Add compose_cmd_wrapper_include field to StackConfig.
- Fix wrapper placeholder text not displaying in MonacoEditor.

* align configuration by removing bold label stuff

* confirm update monaco readonly

* dockerfile binaries / ui images default to :2

* fmt

* refresh server cache lint

* fmt

* disabling send alerts should also disable in UI

* clean up disabled alerting

* deploy 2.0.0-dev-131

* mogh_pki 1.1.3 safer copy from slice

* fix dev container issues on 2.0.0 (#1238)

* fix node version and yarn build in dev container

* ensure keys directories exist and are writable in dev container

* update dev container  image (necessary to fix compiler error)

* fix CORS error in dev container

* more dev container fixes

KOMODO_SESSION_ALLOW_CROSS_SITE: true needed to properly run on Firefox

* fix devcontainer port

* update config pages to use "Webhooks" consistently and fix acronym casing (#1239)

Co-authored-by: Maxwell Becker <49575486+mbecker20@users.noreply.github.com>

* update local dev setup instructions (#1240)

* example mongo deploy dev

* check binary URL (#1116)

* install script improve failed download binary log

* align cli installer with periphery installer

* improve terminal page create experience

* deploy 2.0.0-dev-132

* improve mobile updates with full size query

* fix some internal table wrapping

* fix schedule expression examples

* Add swarm updates / alerts filter

* filter by update available uses location hash instead of localstorage

* sync commit preserve meta "deploy" and "after"

* UI fixes and tweaks

* deploy 2.0.0-dev-133

* add error to warn log

* Stack / deployment should inherit specific Swarm permissions

* Fix inline span formatting (used in logs / errors)

* refactor resource sync pending deploy for better display

* deploy 2.0.0-dev-134

* improve terminal error handling

* deploy 2.0.0-dev-135

* dedicated docs page for v2

* warning about failing to include init: true

* Limit Periphery IPs in Advanced config

* refine setup guide

* 2.0.0

---------

Co-authored-by: Shlee <github@shl.ee>
Co-authored-by: Yujia Qiao <code@rapiz.me>
Co-authored-by: ChanningHe <52875777+ChanningHe@users.noreply.github.com>
Co-authored-by: Steven Loria <sloria1@pm.me>
Co-authored-by: Andreas Brett <andreasbrett@users.noreply.github.com>
2026-03-24 05:50:10 -07:00
1032 changed files with 61270 additions and 62307 deletions
+9 -3
View File
@@ -1,6 +1,6 @@
services:
dev:
image: mcr.microsoft.com/devcontainers/rust:1-1-bullseye
image: mcr.microsoft.com/devcontainers/rust:1-bookworm
volumes:
# Mount the root folder that contains .git
- ../:/workspace:cached
@@ -10,13 +10,19 @@ services:
- stacks:/etc/komodo/stacks
command: sleep infinity
ports:
- "9121:9121"
- "9120:9120"
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
# ...
@@ -30,4 +36,4 @@ volumes:
data:
repo-cache:
repos:
stacks:
stacks:
+9 -3
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": "20.12.2"
"version": "22.12.0"
},
"ghcr.io/devcontainers-community/features/deno:1": {
@@ -28,7 +28,8 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
9121
5173,
9120
],
// Use 'postCreateCommand' to run commands after the container is created.
@@ -36,11 +37,16 @@
"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"
}
}
+7 -1
View File
@@ -1,3 +1,9 @@
#!/bin/sh
cargo install typeshare-cli
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
+56
View File
@@ -0,0 +1,56 @@
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
-4
View File
@@ -6,8 +6,4 @@ deno.lock
.env.development
.DS_Store
.idea
/frontend/build
/lib/ts_client/build
.dev
+14 -14
View File
@@ -106,62 +106,62 @@
"problemMatcher": []
},
{
"label": "Init Frontend Client",
"label": "Init UI Client",
"type": "shell",
"command": "yarn link komodo_client && yarn install",
"options": {
"cwd": "${workspaceFolder}/frontend",
"cwd": "${workspaceFolder}/ui",
},
"problemMatcher": []
},
{
"label": "Init Frontend",
"label": "Init UI",
"dependsOn": [
"Build TS Client Types",
"Init TS Client",
"Init Frontend Client"
"Init UI Client"
],
"dependsOrder": "sequence",
"problemMatcher": []
},
{
"label": "Build Frontend",
"label": "Build UI",
"type": "shell",
"command": "yarn build",
"options": {
"cwd": "${workspaceFolder}/frontend",
"cwd": "${workspaceFolder}/ui",
},
"problemMatcher": []
},
{
"label": "Prepare Frontend For Run",
"label": "Prepare UI For Run",
"type": "shell",
"command": "cp -r ./client/core/ts/dist/. frontend/public/client/.",
"command": "cp -r ./client/core/ts/dist/. ui/public/client/.",
"options": {
"cwd": "${workspaceFolder}",
},
"dependsOn": [
"Build TS Client Types",
"Build Frontend"
"Build UI"
],
"dependsOrder": "sequence",
"problemMatcher": []
},
{
"label": "Run Frontend",
"label": "Run UI",
"type": "shell",
"command": "yarn dev",
"options": {
"cwd": "${workspaceFolder}/frontend",
"cwd": "${workspaceFolder}/ui",
},
"dependsOn": ["Prepare Frontend For Run"],
"dependsOn": ["Prepare UI For Run"],
"problemMatcher": []
},
{
"label": "Init",
"dependsOn": [
"Build Backend",
"Init Frontend"
"Init UI"
],
"dependsOrder": "sequence",
"problemMatcher": []
@@ -171,7 +171,7 @@
"dependsOn": [
"Run Core",
"Run Periphery",
"Run Frontend"
"Run UI"
],
"problemMatcher": []
},
Generated
+1318 -1018
View File
File diff suppressed because it is too large Load Diff
+55 -72
View File
@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "2.0.0-dev-102"
version = "2.1.0"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -22,106 +22,89 @@ strip = "debuginfo"
# LOCAL
komodo_client = { path = "client/core/rs" }
periphery_client = { path = "client/periphery/rs" }
environment_file = { path = "lib/environment_file" }
environment = { path = "lib/environment" }
interpolate = { path = "lib/interpolate" }
secret_file = { path = "lib/secret_file" }
validations = { path = "lib/validations" }
formatting = { path = "lib/formatting" }
rate_limit = { path = "lib/rate_limit" }
transport = { path = "lib/transport" }
database = { path = "lib/database" }
encoding = { path = "lib/encoding" }
response = { path = "lib/response" }
command = { path = "lib/command" }
config = { path = "lib/config" }
logger = { path = "lib/logger" }
cache = { path = "lib/cache" }
noise = { path = "lib/noise" }
git = { path = "lib/git" }
# MOGH
serror = { version = "0.5.3", default-features = false }
slack = { version = "2.0.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
mogh_error = { version = "1.0.3", default-features = false }
derive_default_builder = "0.1.8"
derive_empty_traits = "0.1.0"
async_timing_util = "1.1.0"
partial_derive2 = "0.4.3"
derive_variants = "1.0.0"
mogh_auth_client = "1.2.2"
mogh_auth_server = "1.2.13"
mogh_secret_file = "1.0.1"
mogh_validations = "1.0.1"
mogh_rate_limit = "1.0.1"
partial_derive2 = "0.4.5"
mongo_indexed = "2.0.2"
resolver_api = "3.0.0"
mogh_resolver = "1.0.0"
mogh_config = "1.0.5"
mogh_logger = "1.3.3"
mogh_server = "1.4.5"
toml_pretty = "2.0.0"
mogh_cache = "1.1.1"
mogh_pki = "1.1.3"
mungos = "3.2.2"
svi = "1.2.0"
# ASYNC
reqwest = { version = "0.12.24", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
tokio = { version = "1.48.0", features = ["full"] }
tokio-util = { version = "0.7.17", features = ["io", "codec"] }
tokio-stream = { version = "0.1.17", features = ["sync"] }
pin-project-lite = "0.2.16"
futures-util = "0.3.31"
arc-swap = "1.7.1"
reqwest = { version = "0.13.2", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
tokio = { version = "1.50.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.17"
futures-util = "0.3.32"
arc-swap = "1.9.0"
# SERVER
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
tower-http = { version = "0.6.8", features = ["fs", "cors", "set-header"] }
axum = { version = "0.8.7", features = ["ws", "json", "macros"] }
axum-extra = { version = "0.12.2", features = ["typed-header"] }
axum-server = { version = "0.8.0", features = ["tls-rustls"] }
tower-sessions = "0.14.0"
tokio-tungstenite = { version = "0.29.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.4.0"
# SER/DE
ipnetwork = { version = "0.21.1", features = ["serde"] }
indexmap = { version = "2.12.1", features = ["serde"] }
indexmap = { version = "2.13.0", features = ["serde"] }
serde = { version = "1.0.227", features = ["derive"] }
strum = { version = "0.27.2", features = ["derive"] }
strum = { version = "0.28.0", features = ["derive"] }
bson = { version = "2.15.0" } # must keep in sync with mongodb version
toml = "1.1.2"
serde_yaml_ng = "0.10.0"
serde_json = "1.0.145"
serde_qs = "0.15.0"
toml = "0.9.8"
url = "2.5.7"
serde_json = "1.0.149"
serde_qs = "1.1.0"
url = "2.5.8"
# ERROR
anyhow = "1.0.100"
thiserror = "2.0.17"
anyhow = "1.0.102"
thiserror = "2.0.18"
# LOGGING
opentelemetry-otlp = { version = "0.31.0", features = ["tls-roots", "reqwest-rustls"] }
opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] }
tracing-subscriber = { version = "0.3.22", features = ["json"] }
opentelemetry-semantic-conventions = "0.31.0"
tracing-opentelemetry = "0.32.0"
opentelemetry = "0.31.0"
tracing = "0.1.43"
tracing = "0.1.44"
# CONFIG
clap = { version = "4.5.53", features = ["derive"] }
clap = { version = "4.5.60", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
# CRYPTO / AUTH
webauthn-rs = { version = "0.5.4", features = ["danger-allow-state-serialisation"] }
openidconnect = { version = "4.0.1", features = ["accept-rfc3339-timestamps"] }
uuid = { version = "1.19.0", features = ["v4", "fast-rng", "serde"] }
jsonwebtoken = { version = "10.2.0", features = ["aws_lc_rs"] } # locked back with octorust
rustls = { version = "0.23.35", features = ["aws-lc-rs"] }
pem-rfc7468 = { version = "1.0.0", features = ["alloc"] }
totp-rs = { version = "5.7.0", features = ["qr"] }
webauthn-rs-proto = "0.5.4"
base64urlsafedata = "0.5.4"
data-encoding = "2.9.0"
uuid = { version = "1.23.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.17.1"
pkcs8 = "0.10.2"
snow = "0.10.0"
bcrypt = "0.19.0"
hmac = "0.12.1"
sha1 = "0.10.6"
sha2 = "0.10.9"
rand = "0.9.2"
spki = "0.7.3"
der = "0.7.10"
rand = "0.10.0"
hex = "0.4.3"
# SYSTEM
@@ -129,27 +112,27 @@ hickory-resolver = "0.25.2"
portable-pty = "0.9.0"
shell-escape = "0.1.5"
crossterm = "0.29.0"
bollard = "0.19.4"
sysinfo = "0.37.1"
bollard = "0.20.2"
sysinfo = "0.38.4"
shlex = "1.3.0"
# CLOUD
aws-config = "1.8.12"
aws-sdk-ec2 = "1.196.0"
aws-credential-types = "1.2.11"
aws-config = "1.8.15"
aws-sdk-ec2 = "1.220.1"
aws-credential-types = "1.2.14"
## CRON
english-to-cron = "0.1.7"
chrono-tz = "0.10.4"
chrono = "0.4.42"
chrono = "0.4.44"
croner = "3.0.1"
# MISC
async-compression = { version = "0.4.35", features = ["tokio", "gzip"] }
async-compression = { version = "0.4.41", features = ["tokio", "gzip"] }
derive_builder = "0.20.2"
comfy-table = "7.2.1"
typeshare = "1.0.4"
comfy-table = "7.2.2"
typeshare = "1.0.5"
wildcard = "0.3.0"
colored = "3.0.0"
bytes = "1.11.0"
regex = "1.12.2"
colored = "3.1.1"
bytes = "1.11.1"
regex = "1.12.3"
+1 -1
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.91.1-bookworm AS builder
FROM rust:1.94.1-bookworm AS builder
RUN cargo install cargo-strip
WORKDIR /builder
+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.90.0-bookworm AS chef
FROM lukemathwalker/cargo-chef:latest-rust-1.94.1-bookworm AS chef
WORKDIR /builder
# Plan just the RECIPE to see if things have changed
+7 -5
View File
@@ -14,12 +14,12 @@ path = "src/main.rs"
[dependencies]
# local
environment_file.workspace = true
komodo_client.workspace = true
komodo_client = { workspace = true, features = ["cli"] }
mogh_secret_file.workspace = true
mogh_pki.workspace = true
database.workspace = true
config.workspace = true
logger.workspace = true
noise.workspace = true
mogh_config.workspace = true
mogh_logger.workspace = true
# external
futures-util.workspace = true
comfy-table.workspace = true
@@ -32,7 +32,9 @@ 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.91.1-bullseye AS builder
FROM rust:1.94.1-bullseye AS builder
RUN cargo install cargo-strip
WORKDIR /builder
+1 -1
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:latest
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64
ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
+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:latest
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
# This is required to work with COPY --from
FROM ${BINARIES_IMAGE} AS binaries
+13
View File
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,101 @@
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
@@ -0,0 +1,13 @@
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
}
}
}
@@ -0,0 +1,46 @@
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(())
}
+5
View File
@@ -358,6 +358,11 @@ async fn v1_downgrade(yes: bool) -> anyhow::Result<()> {
.await
.context("Failed to downgrade Server schema")?;
db.collection::<Document>("Deployment")
.update_many(doc! {}, doc! { "$set": { "info": null } })
.await
.context("Failed to downgrade Deployment schema")?;
info!(
"V1 Downgrade complete. Ready to downgrade to komodo-core:1 ✅"
);
+131 -550
View File
@@ -28,558 +28,139 @@ pub async fn handle(
}
println!("\n{}: Execution", "Mode".dimmed());
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())
}
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!(),
}
}};
}
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!(),
};
let res = handle_execution!(
execute: [
RunAction,
RunProcedure,
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,
],
);
match res {
Ok(ExecutionResult::Single(update)) => {
+2
View File
@@ -15,6 +15,8 @@ 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;
+87 -42
View File
@@ -2,7 +2,7 @@ use anyhow::{Context, anyhow};
use colored::Colorize;
use komodo_client::{
api::{
read::{ListAllDockerContainers, ListServers},
read::{GetServer, ListAllDockerContainers, ListServers},
terminal::InitTerminal,
},
entities::{
@@ -26,7 +26,7 @@ pub async fn handle_connect(
recreate,
}: &Connect,
) -> anyhow::Result<()> {
handle_terminal_forwarding(async {
handle_terminal_forwarding(server, async {
super::komodo_client()
.await?
.connect_server_terminal(
@@ -56,25 +56,28 @@ pub async fn handle_exec(
}: &Exec,
) -> anyhow::Result<()> {
let server = get_server(server.clone(), container).await?;
handle_terminal_forwarding(async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: Some(shell.to_string()),
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Exec),
}),
)
.await
})
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
},
)
.await
}
@@ -86,25 +89,28 @@ pub async fn handle_attach(
}: &Attach,
) -> anyhow::Result<()> {
let server = get_server(server.clone(), container).await?;
handle_terminal_forwarding(async {
super::komodo_client()
.await?
.connect_container_terminal(
server,
container.to_string(),
None,
Some(InitTerminal {
command: None,
recreate: if *recreate {
TerminalRecreateMode::Always
} else {
TerminalRecreateMode::DifferentCommand
},
mode: Some(ContainerTerminalMode::Attach),
}),
)
.await
})
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
},
)
.await
}
@@ -132,11 +138,14 @@ async fn get_server(
}
if containers.len() == 1 {
return containers
let server_id = containers
.pop()
.context("Shouldn't happen")?
.server_id
.context("Container doesn't have server_id");
.context("Container doesn't have server_id")?;
let server_name =
client.read(GetServer { server: server_id }).await?.name;
return Ok(server_name);
}
let servers = containers
@@ -163,6 +172,7 @@ 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
@@ -247,6 +257,16 @@ 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
{
@@ -297,6 +317,31 @@ 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")?;
+6 -3
View File
@@ -3,7 +3,6 @@ use std::{path::PathBuf, sync::OnceLock};
use anyhow::Context;
use clap::Parser;
use colored::Colorize;
use environment_file::maybe_read_item_from_file;
use komodo_client::entities::{
config::{
DatabaseConfig,
@@ -14,6 +13,7 @@ use komodo_client::entities::{
},
logger::LogConfig,
};
use mogh_secret_file::maybe_read_item_from_file;
pub fn cli_args() -> &'static CliArgs {
static CLI_ARGS: OnceLock<CliArgs> = OnceLock::new();
@@ -74,7 +74,7 @@ pub fn cli_config() -> &'static CliConfig {
"Config File Keywords".dimmed(),
);
}
let mut unparsed_config = (config::ConfigLoader {
let mut unparsed_config = (mogh_config::ConfigLoader {
paths: &config_paths
.iter()
.map(PathBuf::as_path)
@@ -166,7 +166,7 @@ pub fn cli_config() -> &'static CliConfig {
else {
panic!("Profile config is not Object type.");
};
config::merge_config(
mogh_config::merge_config(
unparsed_config,
profile_config.clone(),
env.komodo_cli_merge_nested_config,
@@ -264,6 +264,9 @@ 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),
+9 -2
View File
@@ -12,7 +12,10 @@ mod config;
async fn app() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
logger::init(&config::cli_config().cli_logging)?;
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();
let debug_load =
@@ -42,6 +45,7 @@ async fn app() -> anyhow::Result<()> {
}
Ok(())
}
args::Command::CoreInfo => command::core_info::handle().await,
args::Command::Container(container) => {
command::container::handle(container).await
}
@@ -52,6 +56,9 @@ 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
}
@@ -65,7 +72,7 @@ async fn app() -> anyhow::Result<()> {
command::terminal::handle_attach(attach).await
}
args::Command::Key { command } => {
noise::key::command::handle(command).await
mogh_pki::cli::handle(command, mogh_pki::PkiKind::Mutual).await
}
args::Command::Database { command } => {
command::database::handle(command).await
+16 -21
View File
@@ -15,50 +15,45 @@ path = "src/main.rs"
[dependencies]
# local
komodo_client = { workspace = true, features = ["mongo"] }
komodo_client = { workspace = true, features = ["core"] }
periphery_client.workspace = true
environment_file.workspace = true
mogh_validations.workspace = true
interpolate.workspace = true
secret_file.workspace = true
validations.workspace = true
mogh_secret_file.workspace = true
formatting.workspace = true
rate_limit.workspace = true
mogh_rate_limit.workspace = true
transport.workspace = true
database.workspace = true
encoding.workspace = true
response.workspace = true
command.workspace = true
config.workspace = true
logger.workspace = true
cache.workspace = true
noise.workspace = true
mogh_config.workspace = true
mogh_logger.workspace = true
mogh_cache.workspace = true
mogh_pki.workspace = true
git.workspace = true
# mogh
serror = { workspace = true, features = ["axum"] }
mogh_error = { workspace = true, features = ["axum"] }
mogh_auth_client = { workspace = true, features = ["utoipa"] }
mogh_auth_server.workspace = true
async_timing_util.workspace = true
partial_derive2.workspace = true
derive_variants.workspace = true
resolver_api.workspace = true
mogh_resolver.workspace = true
mogh_server.workspace = true
toml_pretty.workspace = true
slack.workspace = true
svi.workspace = true
# external
aws-credential-types.workspace = true
english-to-cron.workspace = true
tower-sessions.workspace = true
openidconnect.workspace = true
data-encoding.workspace = true
serde_yaml_ng.workspace = true
jsonwebtoken.workspace = true
utoipa-scalar.workspace = true
futures-util.workspace = true
axum-server.workspace = true
aws-sdk-ec2.workspace = true
urlencoding.workspace = true
webauthn-rs.workspace = true
aws-config.workspace = true
tokio-util.workspace = true
axum-extra.workspace = true
tower-http.workspace = true
serde_json.workspace = true
typeshare.workspace = true
chrono-tz.workspace = true
@@ -70,12 +65,12 @@ colored.workspace = true
tracing.workspace = true
reqwest.workspace = true
dotenvy.workspace = true
totp-rs.workspace = true
anyhow.workspace = true
bcrypt.workspace = true
croner.workspace = true
chrono.workspace = true
bcrypt.workspace = true
rustls.workspace = true
utoipa.workspace = true
bytes.workspace = true
tokio.workspace = true
serde.workspace = true
+6 -6
View File
@@ -1,7 +1,7 @@
## All in one, multi stage compile + runtime Docker build for your architecture.
# Build Core
FROM rust:1.91.1-trixie AS core-builder
FROM rust:1.94.1-trixie AS core-builder
RUN cargo install cargo-strip
WORKDIR /builder
@@ -17,13 +17,13 @@ RUN cargo build -p komodo_core --release && \
cargo build -p komodo_cli --release && \
cargo strip
# Build Frontend
FROM node:20.12-alpine AS frontend-builder
# Build UI
FROM node:22.12-alpine AS ui-builder
WORKDIR /builder
COPY ./frontend ./frontend
COPY ./ui ./ui
COPY ./client/core/ts ./client
RUN cd client && yarn && yarn build && yarn link
RUN cd frontend && yarn link komodo_client && yarn && yarn build
RUN cd ui && yarn link komodo_client && yarn && yarn build
# Final Image
FROM debian:trixie-slim
@@ -37,7 +37,7 @@ WORKDIR /app
# Copy
COPY ./config/core.config.toml /config/.default.config.toml
COPY --from=frontend-builder /builder/frontend/dist /app/frontend
COPY --from=ui-builder /builder/ui/dist /app/ui
COPY --from=core-builder /builder/target/release/core /usr/local/bin/core
COPY --from=core-builder /builder/target/release/km /usr/local/bin/km
COPY --from=denoland/deno:bin /deno /usr/local/bin/deno
+5 -5
View File
@@ -2,15 +2,15 @@
## 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:latest
ARG FRONTEND_IMAGE=ghcr.io/moghtech/komodo-frontend:latest
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
ARG UI_IMAGE=ghcr.io/moghtech/komodo-ui:2
ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64
ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
# This is required to work with COPY --from
FROM ${X86_64_BINARIES} AS x86_64
FROM ${AARCH64_BINARIES} AS aarch64
FROM ${FRONTEND_IMAGE} AS frontend
FROM ${UI_IMAGE} AS ui
# Final Image
FROM debian:trixie-slim
@@ -33,9 +33,9 @@ COPY --from=x86_64 /km /app/km/linux/amd64
COPY --from=aarch64 /km /app/km/linux/arm64
RUN mv /app/km/${TARGETPLATFORM} /usr/local/bin/km && rm -r /app/km
# Copy default config / static frontend / deno binary
# Copy default config / static ui / deno binary
COPY ./config/core.config.toml /config/.default.config.toml
COPY --from=frontend /frontend /app/frontend
COPY --from=ui /ui /app/ui
COPY --from=denoland/deno:bin /deno /usr/local/bin/deno
# Set $DENO_DIR and preload external Deno deps
+6 -6
View File
@@ -1,18 +1,18 @@
## 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:latest
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
# This is required to work with COPY --from
FROM ${BINARIES_IMAGE} AS binaries
# Build Frontend
FROM node:20.12-alpine AS frontend-builder
# Build UI
FROM node:22.12-alpine AS ui-builder
WORKDIR /builder
COPY ./frontend ./frontend
COPY ./ui ./ui
COPY ./client/core/ts ./client
RUN cd client && yarn && yarn build && yarn link
RUN cd frontend && yarn link komodo_client && yarn && yarn build
RUN cd ui && yarn link komodo_client && yarn && yarn build
FROM debian:trixie-slim
@@ -22,7 +22,7 @@ RUN sh ./debian-deps.sh && rm ./debian-deps.sh
# Copy
COPY ./config/core.config.toml /config/.default.config.toml
COPY --from=frontend-builder /builder/frontend/dist /app/frontend
COPY --from=ui-builder /builder/ui/dist /app/ui
COPY --from=binaries /core /usr/local/bin/core
COPY --from=binaries /km /usr/local/bin/km
COPY --from=denoland/deno:bin /deno /usr/local/bin/deno
+93 -19
View File
@@ -16,26 +16,24 @@ pub async fn send_alert(
"{level} | If you see this message, then Alerter **{name}** is **working**\n{link}"
)
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
AlertData::SwarmUnhealthy { id, name, err } => {
let link = resource_link(ResourceTargetVariant::Swarm, id);
match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | **{name}**{region} | Periphery version now matches Core version ✅\n{link}"
"{level} | Swarm **{name}** is now **healthy**\n{link}"
)
}
_ => {
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\n**error**: {e:#?}"))
.unwrap_or_default();
format!(
"{level} | **{name}**{region} | Version mismatch detected ⚠️\nPeriphery: **{server_version}** | Core: **{core_version}**\n{link}"
"{level} | Swarm **{name}** is **unhealthy** ❌\n{link}{err}"
)
}
_ => unreachable!(),
}
}
AlertData::ServerUnreachable {
@@ -64,6 +62,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::ServerCpu {
id,
name,
@@ -108,6 +128,8 @@ pub async fn send_alert(
AlertData::ContainerStateChange {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
@@ -115,37 +137,64 @@ pub async fn send_alert(
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let to = fmt_docker_container_state(to);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"📦 Deployment **{name}** is now **{to}**\nserver: **{server_name}**\nprevious: **{from}**\n{link}"
"📦 Deployment **{name}** is now **{to}**{target}\nprevious: **{from}**\n{link}"
)
}
AlertData::DeploymentImageUpdateAvailable {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"⬆ Deployment **{name}** has an update available\nserver: **{server_name}**\nimage: **{image}**\n{link}"
"⬆ Deployment **{name}** has an update available{target}\nimage: **{image}**\n{link}"
)
}
AlertData::DeploymentAutoUpdated {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"⬆ Deployment **{name}** was updated automatically ⏫\nserver: **{server_name}**\nimage: **{image}**\n{link}"
"⬆ Deployment **{name}** was updated automatically ⏫{target}\nimage: **{image}**\n{link}"
)
}
AlertData::StackStateChange {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
@@ -153,26 +202,44 @@ pub async fn send_alert(
} => {
let link = resource_link(ResourceTargetVariant::Stack, id);
let to = fmt_stack_state(to);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"🥞 Stack **{name}** is now {to}\nserver: **{server_name}**\nprevious: **{from}**\n{link}"
"🥞 Stack **{name}** is now {to}{target}\nprevious: **{from}**\n{link}"
)
}
AlertData::StackImageUpdateAvailable {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
service,
image,
} => {
let link = resource_link(ResourceTargetVariant::Stack, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"⬆ Stack **{name}** has an update available\nserver: **{server_name}**\nservice: **{service}**\nimage: **{image}**\n{link}"
"⬆ Stack **{name}** has an update available{target}\nservice: **{service}**\nimage: **{image}**\n{link}"
)
}
AlertData::StackAutoUpdated {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
images,
@@ -181,8 +248,15 @@ pub async fn send_alert(
let images_label =
if images.len() > 1 { "images" } else { "image" };
let images = images.join(", ");
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: **{swarm}**")
} else if let Some(server) = server_name {
format!("\nserver: **{server}**")
} else {
String::new()
};
format!(
"⬆ Stack **{name}** was updated automatically ⏫\nserver: **{server_name}**\n{images_label}: **{images}**\n{link}"
"⬆ Stack **{name}** was updated automatically ⏫{target}\n{images_label}: **{images}**\n{link}"
)
}
AlertData::AwsBuilderTerminationFailed {
@@ -264,7 +338,7 @@ pub async fn send_alert(
let sanitized_error =
svi::replace_in_string(&format!("{e:?}"), &replacers);
anyhow::Error::msg(format!(
"Error with slack request: {sanitized_error}"
"Error with request to Discord: {sanitized_error}"
))
})
}
+96 -25
View File
@@ -1,6 +1,5 @@
use anyhow::{Context, anyhow};
use database::mungos::{find::find_collect, mongodb::bson::doc};
use derive_variants::ExtractVariant;
use futures_util::future::join_all;
use interpolate::Interpolator;
use komodo_client::entities::{
@@ -81,14 +80,14 @@ pub async fn send_alert_to_alerter(
return Ok(());
}
let alert_type = alert.data.extract_variant();
let alert_variant: AlertDataVariant = (&alert.data).into();
// In the test case, we don't want the filters inside this
// block to stop the test from being sent to the alerting endpoint.
if alert_type != AlertDataVariant::Test {
if alert_variant != AlertDataVariant::Test {
// Don't send if alert type not configured on the alerter
if !alerter.config.alert_types.is_empty()
&& !alerter.config.alert_types.contains(&alert_type)
&& !alerter.config.alert_types.contains(&alert_variant)
{
return Ok(());
}
@@ -252,26 +251,22 @@ fn standard_alert_content(alert: &Alert) -> String {
"{level} | If you see this message, then Alerter {name} is working\n{link}",
)
}
AlertData::ServerVersionMismatch {
id,
name,
region,
server_version,
core_version,
} => {
let region = fmt_region(region);
let link = resource_link(ResourceTargetVariant::Server, id);
AlertData::SwarmUnhealthy { id, name, err } => {
let link = resource_link(ResourceTargetVariant::Swarm, id);
match alert.level {
SeverityLevel::Ok => {
format!("{level} | Swarm {name} is now healthy\n{link}")
}
SeverityLevel::Critical => {
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
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}"
"{level} | Swarm {name} is unhealthy ❌\n{link}{err}"
)
}
_ => unreachable!(),
}
}
AlertData::ServerUnreachable {
@@ -298,6 +293,28 @@ fn standard_alert_content(alert: &Alert) -> String {
_ => 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,
@@ -342,6 +359,8 @@ fn standard_alert_content(alert: &Alert) -> String {
AlertData::ContainerStateChange {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
@@ -349,37 +368,64 @@ fn standard_alert_content(alert: &Alert) -> String {
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let to_state = fmt_docker_container_state(to);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"📦Deployment {name} is now {to_state}\nserver: {server_name}\nprevious: {from}\n{link}",
"📦Deployment {name} is now {to_state}{target}\nprevious: {from}\n{link}",
)
}
AlertData::DeploymentImageUpdateAvailable {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"⬆ Deployment {name} has an update available\nserver: {server_name}\nimage: {image}\n{link}",
"⬆ Deployment {name} has an update available{target}\nimage: {image}\n{link}",
)
}
AlertData::DeploymentAutoUpdated {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let link = resource_link(ResourceTargetVariant::Deployment, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"⬆ Deployment {name} was updated automatically\nserver: {server_name}\nimage: {image}\n{link}",
"⬆ Deployment {name} was updated automatically{target}\nimage: {image}\n{link}",
)
}
AlertData::StackStateChange {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
@@ -387,26 +433,44 @@ fn standard_alert_content(alert: &Alert) -> String {
} => {
let link = resource_link(ResourceTargetVariant::Stack, id);
let to_state = fmt_stack_state(to);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"🥞 Stack {name} is now {to_state}\nserver: {server_name}\nprevious: {from}\n{link}",
"🥞 Stack {name} is now {to_state}{target}\nprevious: {from}\n{link}",
)
}
AlertData::StackImageUpdateAvailable {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
service,
image,
} => {
let link = resource_link(ResourceTargetVariant::Stack, id);
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"⬆ Stack {name} has an update available\nserver: {server_name}\nservice: {service}\nimage: {image}\n{link}",
"⬆ Stack {name} has an update available{target}\nservice: {service}\nimage: {image}\n{link}",
)
}
AlertData::StackAutoUpdated {
id,
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
images,
@@ -415,8 +479,15 @@ fn standard_alert_content(alert: &Alert) -> String {
let images_label =
if images.len() > 1 { "images" } else { "image" };
let images_str = images.join(", ");
let target = if let Some(swarm) = swarm_name {
format!("\nswarm: {swarm}")
} else if let Some(server) = server_name {
format!("\nserver: {server}")
} else {
String::new()
};
format!(
"⬆ Stack {name} was updated automatically ⏫\nserver: {server_name}\n{images_label}: {images_str}\n{link}",
"⬆ Stack {name} was updated automatically ⏫{target}\n{images_label}: {images_str}\n{link}",
)
}
AlertData::AwsBuilderTerminationFailed {
+1 -1
View File
@@ -31,7 +31,7 @@ pub async fn send_alert(
let sanitized_error =
svi::replace_in_string(&format!("{e:?}"), &replacers);
anyhow::Error::msg(format!(
"Error with slack request: {sanitized_error}"
"Error with request to Ntfy: {sanitized_error}"
))
})
}
+1 -1
View File
@@ -28,7 +28,7 @@ pub async fn send_alert(
let sanitized_error =
svi::replace_in_string(&format!("{e:?}"), &replacers);
anyhow::Error::msg(format!(
"Error with slack request: {sanitized_error}"
"Error with request to Pushover: {sanitized_error}"
))
})
}
+145 -64
View File
@@ -24,6 +24,82 @@ pub async fn send_alert(
];
(text, blocks.into())
}
AlertData::SwarmUnhealthy { id, name, err } => {
match alert.level {
SeverityLevel::Ok => {
let text =
format!("{level} | Swarm *{name}* is now *healthy*");
let blocks = vec![
Block::header(level),
Block::section(format!(
"Swarm *{name}* is now *healthy*"
)),
];
(text, blocks.into())
}
SeverityLevel::Critical => {
let text =
format!("{level} | Swarm *{name}* is *unhealthy* ❌");
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
let blocks = vec![
Block::header(level),
Block::section(format!(
"Swarm *{name}* is *unhealthy* ❌{err}"
)),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
(text, blocks.into())
}
_ => unreachable!(),
}
}
AlertData::ServerUnreachable {
id,
name,
region,
err,
} => {
let region = fmt_region(region);
match alert.level {
SeverityLevel::Ok => {
let text =
format!("{level} | *{name}*{region} is now *connected*");
let blocks = vec![
Block::header(level),
Block::section(format!(
"*{name}*{region} is now *connected*"
)),
];
(text, blocks.into())
}
SeverityLevel::Critical => {
let text =
format!("{level} | *{name}*{region} is *unreachable* ❌");
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
let blocks = vec![
Block::header(level),
Block::section(format!(
"*{name}*{region} is *unreachable* ❌{err}"
)),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
(text, blocks.into())
}
_ => unreachable!(),
}
}
AlertData::ServerVersionMismatch {
id,
name,
@@ -53,47 +129,6 @@ pub async fn send_alert(
];
(text, blocks.into())
}
AlertData::ServerUnreachable {
id,
name,
region,
err,
} => {
let region = fmt_region(region);
match alert.level {
SeverityLevel::Ok => {
let text =
format!("{level} | *{name}*{region} is now *connected*");
let blocks = vec![
Block::header(level),
Block::section(format!(
"*{name}*{region} is now *connnected*"
)),
];
(text, blocks.into())
}
SeverityLevel::Critical => {
let text =
format!("{level} | *{name}*{region} is *unreachable* ❌");
let err = err
.as_ref()
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
let blocks = vec![
Block::header(level),
Block::section(format!(
"*{name}*{region} is *unreachable* ❌{err}"
)),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
(text, blocks.into())
}
_ => unreachable!(),
}
}
AlertData::ServerCpu {
id,
name,
@@ -239,19 +274,26 @@ pub async fn send_alert(
}
AlertData::ContainerStateChange {
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
to,
id,
..
} => {
let to = fmt_docker_container_state(to);
let text = format!("📦 Container *{name}* is now *{to}*");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: {server_name}\nprevious: {from}",
)),
Block::section(format!("{target}previous: {from}",)),
Block::section(resource_link(
ResourceTargetVariant::Deployment,
id,
@@ -262,17 +304,24 @@ pub async fn send_alert(
AlertData::DeploymentImageUpdateAvailable {
id,
name,
server_name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let text =
format!("⬆ Deployment *{name}* has an update available");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: *{server_name}*\nimage: *{image}*",
)),
Block::section(format!("{target}image: *{image}*",)),
Block::section(resource_link(
ResourceTargetVariant::Deployment,
id,
@@ -283,17 +332,24 @@ pub async fn send_alert(
AlertData::DeploymentAutoUpdated {
id,
name,
server_name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
image,
} => {
let text =
format!("⬆ Deployment *{name}* was updated automatically ⏫");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: *{server_name}*\nimage: *{image}*",
)),
Block::section(format!("{target}image: *{image}*",)),
Block::section(resource_link(
ResourceTargetVariant::Deployment,
id,
@@ -303,19 +359,26 @@ pub async fn send_alert(
}
AlertData::StackStateChange {
name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
from,
to,
id,
..
} => {
let to = fmt_stack_state(to);
let text = format!("🥞 Stack *{name}* is now *{to}*");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: *{server_name}*\nprevious: *{from}*",
)),
Block::section(format!("{target}previous: *{from}*",)),
Block::section(resource_link(
ResourceTargetVariant::Stack,
id,
@@ -326,16 +389,25 @@ pub async fn send_alert(
AlertData::StackImageUpdateAvailable {
id,
name,
server_name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
service,
image,
} => {
let text = format!("⬆ Stack *{name}* has an update available");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: *{server_name}*\nservice: *{service}*\nimage: *{image}*",
"{target}service: *{service}*\nimage: *{image}*",
)),
Block::section(resource_link(
ResourceTargetVariant::Stack,
@@ -347,8 +419,10 @@ pub async fn send_alert(
AlertData::StackAutoUpdated {
id,
name,
server_name,
swarm_id: _swarm_id,
swarm_name,
server_id: _server_id,
server_name,
images,
} => {
let text =
@@ -356,11 +430,18 @@ pub async fn send_alert(
let images_label =
if images.len() > 1 { "images" } else { "image" };
let images = images.join(", ");
let target = if let Some(swarm) = swarm_name {
format!("swarm: *{swarm}*\n")
} else if let Some(server) = server_name {
format!("server: *{server}*\n")
} else {
String::new()
};
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"server: *{server_name}*\n{images_label}: *{images}*",
)),
Block::section(
format!("{target}{images_label}: *{images}*",),
),
Block::section(resource_link(
ResourceTargetVariant::Stack,
id,
@@ -491,7 +572,7 @@ pub async fn send_alert(
let sanitized_error =
svi::replace_in_string(&format!("{e:?}"), &replacers);
anyhow::Error::msg(format!(
"Error with slack request: {sanitized_error}"
"Error with request to Slack: {sanitized_error}"
))
})?;
Ok(())
-353
View File
@@ -1,353 +0,0 @@
use std::{
net::{IpAddr, SocketAddr},
sync::OnceLock,
time::Instant,
};
use anyhow::{Context, anyhow};
use axum::{
Router,
extract::{ConnectInfo, Path},
http::HeaderMap,
routing::post,
};
use data_encoding::BASE32_NOPAD;
use database::{
bson::{doc, to_bson},
mungos::by_id::update_one_by_id,
};
use derive_variants::{EnumVariants, ExtractVariant};
use komodo_client::{api::auth::*, entities::user::User};
use rate_limit::WithFailureRateLimit;
use reqwest::StatusCode;
use resolver_api::Resolve;
use response::Response;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::{AddStatusCode, AddStatusCodeError, Json};
use tower_sessions::Session;
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::{
SessionPasskeyLogin, SessionTotpLogin, SessionUserId,
get_user_id_from_headers,
github::{self, client::github_oauth_client},
google::{self, client::google_oauth_client},
oidc::{self, client::oidc_client},
totp::make_totp,
},
config::core_config,
helpers::query::get_user,
state::{auth_rate_limiter, db_client, jwt_client, webauthn},
};
use super::Variant;
pub struct AuthArgs {
pub headers: HeaderMap,
/// Prefer extracting IP from headers.
/// This IP will be the IP of reverse proxy itself.
pub ip: IpAddr,
/// Per-client session state
pub session: Option<Session>,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants,
)]
#[args(AuthArgs)]
#[response(Response)]
#[error(serror::Error)]
#[variant_derive(Debug)]
#[serde(tag = "type", content = "params")]
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
pub enum AuthRequest {
GetLoginOptions(GetLoginOptions),
SignUpLocalUser(SignUpLocalUser),
LoginLocalUser(LoginLocalUser),
ExchangeForJwt(ExchangeForJwt),
CompleteTotpLogin(CompleteTotpLogin),
CompletePasskeyLogin(CompletePasskeyLogin),
GetUser(GetUser),
}
pub fn router() -> Router {
let mut router = Router::new()
.route("/", post(handler))
.route("/{variant}", post(variant_handler));
if core_config().local_auth {
info!("🔑 Local Login Enabled");
}
if github_oauth_client().is_some() {
info!("🔑 Github Login Enabled");
router = router.nest("/github", github::router())
}
if google_oauth_client().is_some() {
info!("🔑 Google Login Enabled");
router = router.nest("/google", google::router())
}
if core_config().oidc_enabled {
info!("🔑 OIDC Login Enabled");
router = router.nest("/oidc", oidc::router())
}
router
}
async fn variant_handler(
headers: HeaderMap,
session: Session,
info: ConnectInfo<SocketAddr>,
Path(Variant { variant }): Path<Variant>,
Json(params): Json<serde_json::Value>,
) -> serror::Result<axum::response::Response> {
let req: AuthRequest = serde_json::from_value(json!({
"type": variant,
"params": params,
}))?;
handler(headers, session, info, Json(req)).await
}
async fn handler(
headers: HeaderMap,
session: Session,
ConnectInfo(info): ConnectInfo<SocketAddr>,
Json(request): Json<AuthRequest>,
) -> serror::Result<axum::response::Response> {
let timer = Instant::now();
let req_id = Uuid::new_v4();
debug!(
"/auth request {req_id} | METHOD: {:?}",
request.extract_variant()
);
let res = request
.resolve(&AuthArgs {
headers,
ip: info.ip(),
session: Some(session),
})
.await;
if let Err(e) = &res {
debug!("/auth request {req_id} | error: {:#}", e.error);
}
let elapsed = timer.elapsed();
debug!("/auth request {req_id} | resolve time: {elapsed:?}");
res.map(|res| res.0)
}
fn login_options_reponse() -> &'static GetLoginOptionsResponse {
static GET_LOGIN_OPTIONS_RESPONSE: OnceLock<
GetLoginOptionsResponse,
> = OnceLock::new();
GET_LOGIN_OPTIONS_RESPONSE.get_or_init(|| {
let config = core_config();
GetLoginOptionsResponse {
local: config.local_auth,
github: github_oauth_client().is_some(),
google: google_oauth_client().is_some(),
oidc: oidc_client().load().is_some(),
registration_disabled: config.disable_user_registration,
}
})
}
impl Resolve<AuthArgs> for GetLoginOptions {
async fn resolve(
self,
_: &AuthArgs,
) -> serror::Result<GetLoginOptionsResponse> {
Ok(*login_options_reponse())
}
}
impl Resolve<AuthArgs> for ExchangeForJwt {
async fn resolve(
self,
AuthArgs {
headers,
ip,
session,
}: &AuthArgs,
) -> serror::Result<ExchangeForJwtResponse> {
async {
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let SessionUserId(user_id) = session
.remove(SessionUserId::KEY)
.await
.context("Internal session type error")?
.context("Authentication steps must be completed before JWT can be retrieved")?;
jwt_client().encode(user_id).map_err(Into::into)
}
.with_failure_rate_limit_using_headers(
auth_rate_limiter(),
headers,
Some(*ip),
)
.await
}
}
impl Resolve<AuthArgs> for CompleteTotpLogin {
async fn resolve(
self,
AuthArgs {
headers,
ip,
session,
}: &AuthArgs,
) -> serror::Result<CompleteTotpLoginResponse> {
async {
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let SessionTotpLogin { user_id } = session
.get(SessionTotpLogin::KEY)
.await
.context("Internal session type error")?
.context(
"Totp login has not been initiated for this session",
)?;
let user = get_user(&user_id)
.await
.status_code(StatusCode::UNAUTHORIZED)?;
if user.totp.secret.is_empty() {
return Err(
anyhow!("User is not enrolled in totp")
.status_code(StatusCode::BAD_REQUEST),
);
}
let secret_bytes = BASE32_NOPAD
.decode(user.totp.secret.as_bytes())
.context("Failed to decode totp secret to bytes")?;
let totp = make_totp(secret_bytes, None)?;
let valid = totp
.check_current(&self.code)
.context("Failed to check TOTP code validity")?;
if !valid {
return Err(
anyhow!("Invalid totp code")
.status_code(StatusCode::UNAUTHORIZED),
);
}
jwt_client().encode(user_id).map_err(Into::into)
}
.with_failure_rate_limit_using_headers(
auth_rate_limiter(),
headers,
Some(*ip),
)
.await
}
}
impl Resolve<AuthArgs> for CompletePasskeyLogin {
async fn resolve(
self,
AuthArgs {
headers,
ip,
session,
}: &AuthArgs,
) -> serror::Result<CompletePasskeyLoginResponse> {
async {
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let webauthn = webauthn().context(
"No webauthn provider available, invalid KOMODO_HOST config",
)?;
let SessionPasskeyLogin { user_id, state } = session
.get(SessionPasskeyLogin::KEY)
.await
.context("Internal session type error")?
.context(
"Passkey login has not been initiated for this session",
)?;
// The result of this call must be used to
// update the stored passkey info on database.
let update = webauthn
.finish_passkey_authentication(&self.credential, &state)
.context("Failed to validate passkey")?;
let mut passkey = get_user(&user_id)
.await?
.passkey
.passkey
.context("Could not find passkey on database.")?;
passkey.update_credential(&update);
let passkey = to_bson(&passkey)
.context("Failed to serialize passkey to BSON")?;
let update = doc! { "$set": { "passkey.passkey": passkey } };
let _ =
update_one_by_id(&db_client().users, &user_id, update, None)
.await
.context(
"Failed to update user passkey on database after login",
)
.inspect_err(|e| warn!("{e:#}"));
jwt_client().encode(user_id).map_err(Into::into)
}
.with_failure_rate_limit_using_headers(
auth_rate_limiter(),
headers,
Some(*ip),
)
.await
}
}
impl Resolve<AuthArgs> for GetUser {
async fn resolve(
self,
AuthArgs {
headers,
ip,
session: _,
}: &AuthArgs,
) -> serror::Result<User> {
async {
let user_id = get_user_id_from_headers(headers)
.await
.status_code(StatusCode::UNAUTHORIZED)?;
let mut user = get_user(&user_id)
.await
.status_code(StatusCode::UNAUTHORIZED)?;
// Sanitize before sending to client.
user.sanitize();
Ok(user)
}
.with_failure_rate_limit_using_headers(
auth_rate_limiter(),
headers,
Some(*ip),
)
.await
}
}
+92 -72
View File
@@ -6,16 +6,12 @@ use std::{
use anyhow::Context;
use command::run_komodo_standard_command;
use config::merge_objects;
use database::mungos::{
by_id::update_one_by_id, mongodb::bson::to_document,
};
use interpolate::Interpolator;
use komodo_client::{
api::{
execute::{BatchExecutionResponse, BatchRunAction, RunAction},
user::{CreateApiKey, CreateApiKeyResponse, DeleteApiKey},
},
api::execute::{BatchExecutionResponse, BatchRunAction, RunAction},
entities::{
FileFormat, JsonObject,
action::Action,
@@ -29,12 +25,20 @@ use komodo_client::{
},
parsers::parse_key_value_list,
};
use resolver_api::Resolve;
use mogh_auth_client::api::manage::{
CreateApiKey, CreateApiKeyResponse,
};
use mogh_auth_server::api::manage::api_key::{
create_api_key, delete_api_key,
};
use mogh_config::merge_objects;
use mogh_resolver::Resolve;
use tokio::fs;
use crate::{
alert::send_alerts,
api::{execute::ExecuteRequest, user::UserArgs},
api::execute::ExecuteRequest,
auth::KomodoAuthImpl,
config::core_config,
helpers::{
query::{VariablesAndSecrets, get_variables_and_secrets},
@@ -62,15 +66,15 @@ impl Resolve<ExecuteArgs> for BatchRunAction {
"BatchRunAction",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunAction>(&self.pattern, user)
.await?,
@@ -83,7 +87,7 @@ impl Resolve<ExecuteArgs> for RunAction {
"RunAction",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
action = self.action,
@@ -91,8 +95,12 @@ impl Resolve<ExecuteArgs> for RunAction {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut action = get_check_permissions::<Action>(
&self.action,
user,
@@ -135,74 +143,87 @@ impl Resolve<ExecuteArgs> for RunAction {
let args = serde_json::to_string(&args)
.context("Failed to serialize action run arguments")?;
let CreateApiKeyResponse { key, secret } = CreateApiKey {
name: update.id.clone(),
expires: 0,
}
.resolve(&UserArgs {
user: action_user().to_owned(),
session: None,
})
let CreateApiKeyResponse { key, secret } = create_api_key(
&KomodoAuthImpl,
action_user().id.clone(),
CreateApiKey {
name: update.id.clone(),
expires: 0,
},
)
.await?;
let contents = &mut action.config.file_contents;
// Do next steps in seperate error handling block,
// and delete the API key before unwrapping the error.
// If Komodo shuts down during these steps, there will
// be a dangling api key in the DB with user_id: "000000000000000000000002".
// These need to be
let res = async {
let contents = &mut action.config.file_contents;
// Wrap the file contents in the execution context.
*contents = full_contents(contents, &args, &key, &secret);
// Wrap the file contents in the execution context.
*contents = full_contents(contents, &args, &key, &secret);
let replacers =
interpolate(contents, &mut update, key.clone(), secret.clone())
.await?
.into_iter()
.collect::<Vec<_>>();
let replacers = interpolate(
contents,
&mut update,
key.clone(),
secret.clone(),
)
.await?
.into_iter()
.collect::<Vec<_>>();
let file = format!("{}.ts", random_string(10));
let path = core_config().action_directory.join(&file);
let file = format!("{}.ts", random_string(10));
let path = core_config().action_directory.join(&file);
secret_file::write_async(&path, contents)
.await
.with_context(|| {
format!("Failed to write action file to {path:?}")
})?;
mogh_secret_file::write_async(&path, contents)
.await
.with_context(|| {
format!("Failed to write action file to {path:?}")
})?;
let CoreConfig { ssl_enabled, .. } = core_config();
let CoreConfig { ssl_enabled, .. } = core_config();
let https_cert_flag = if *ssl_enabled {
" --unsafely-ignore-certificate-errors=localhost"
} else {
""
};
let https_cert_flag = if *ssl_enabled {
" --unsafely-ignore-certificate-errors=localhost"
} else {
""
};
let reload = if action.config.reload_deno_deps {
" --reload"
} else {
""
};
let reload = if action.config.reload_deno_deps {
" --reload"
} else {
""
};
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()
),
)
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()
),
)
.await;
res.stdout = svi::replace_in_string(&res.stdout, &replacers)
.replace(&key, "<ACTION_API_KEY>");
res.stderr = svi::replace_in_string(&res.stderr, &replacers)
.replace(&secret, "<ACTION_API_SECRET>");
cleanup_run(file + ".js", &path).await;
update.logs.push(res);
update.finalize();
mogh_error::Ok(update)
}
.await;
res.stdout = svi::replace_in_string(&res.stdout, &replacers)
.replace(&key, "<ACTION_API_KEY>");
res.stderr = svi::replace_in_string(&res.stderr, &replacers)
.replace(&secret, "<ACTION_API_SECRET>");
cleanup_run(file + ".js", &path).await;
if let Err(e) = (DeleteApiKey { key })
.resolve(&UserArgs {
user: action_user().to_owned(),
session: None,
})
.await
if let Err(e) =
delete_api_key(&KomodoAuthImpl, &action_user().id, key).await
{
warn!(
"Failed to delete API key after action execution | {:#}",
@@ -210,8 +231,7 @@ impl Resolve<ExecuteArgs> for RunAction {
);
};
update.logs.push(res);
update.finalize();
let update = res?;
// Need to manually update the update before cache refresh,
// and before broadcast with update_update.
@@ -259,7 +279,7 @@ async fn interpolate(
update: &mut Update,
key: String,
secret: String,
) -> serror::Result<HashSet<(String, String)>> {
) -> mogh_error::Result<HashSet<(String, String)>> {
let VariablesAndSecrets {
variables,
mut secrets,
+14 -6
View File
@@ -12,9 +12,9 @@ use komodo_client::{
permission::PermissionLevel,
},
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{
alert::send_alert_to_alerter, helpers::update::update_update,
@@ -28,7 +28,7 @@ impl Resolve<ExecuteArgs> for TestAlerter {
"TestAlerter",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
alerter = self.alerter,
@@ -36,7 +36,11 @@ impl Resolve<ExecuteArgs> for TestAlerter {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let alerter = get_check_permissions::<Alerter>(
&self.alerter,
@@ -94,7 +98,7 @@ impl Resolve<ExecuteArgs> for SendAlert {
"SendAlert",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
request = format!("{self:?}"),
@@ -102,7 +106,11 @@ impl Resolve<ExecuteArgs> for SendAlert {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let alerters = list_full_for_user::<Alerter>(
Default::default(),
+21 -13
View File
@@ -37,8 +37,8 @@ use komodo_client::{
user::auto_redeploy_user,
},
};
use mogh_resolver::Resolve;
use periphery_client::api;
use resolver_api::Resolve;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
@@ -75,15 +75,15 @@ impl Resolve<ExecuteArgs> for BatchRunBuild {
"BatchRunBuild",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunBuild>(&self.pattern, user)
.await?,
@@ -96,7 +96,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
"RunBuild",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
build = self.build,
@@ -104,8 +104,12 @@ impl Resolve<ExecuteArgs> for RunBuild {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut build = get_check_permissions::<Build>(
&self.build,
user,
@@ -431,7 +435,7 @@ async fn handle_early_return(
build_id: String,
build_name: String,
is_cancel: bool,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
update.finalize();
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
@@ -522,7 +526,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
"CancelBuild",
skip(user, update),
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
build = self.build,
@@ -530,8 +534,12 @@ impl Resolve<ExecuteArgs> for CancelBuild {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let build = get_check_permissions::<Build>(
&self.build,
user,
@@ -626,7 +634,7 @@ async fn handle_post_build_redeploy(build_id: &str) {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
}
@@ -660,7 +668,7 @@ async fn validate_account_extract_registry_tokens(
..
}: &Build,
// Maps (domain, account) -> token
) -> serror::Result<Vec<(String, String, String)>> {
) -> mogh_error::Result<Vec<(String, String, String)>> {
let mut res = HashMap::with_capacity(image_registry.capacity());
for (domain, account) in image_registry
+108 -53
View File
@@ -1,7 +1,6 @@
use std::sync::OnceLock;
use anyhow::{Context, anyhow};
use cache::TimeoutCache;
use formatting::format_serror;
use interpolate::Interpolator;
use komodo_client::{
@@ -10,7 +9,8 @@ use komodo_client::{
SwarmOrServer, Version,
build::{Build, ImageRegistryConfig},
deployment::{
Deployment, DeploymentImage, extract_registry_domain,
Deployment, DeploymentImage, DeploymentInfo,
extract_registry_domain,
},
komodo_timestamp, optional_string,
permission::PermissionLevel,
@@ -18,10 +18,11 @@ use komodo_client::{
update::{Log, Update},
},
};
use mogh_cache::TimeoutCache;
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use periphery_client::api;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{
helpers::{
@@ -31,7 +32,7 @@ use crate::{
swarm::swarm_request,
update::update_update,
},
monitor::{update_cache_for_server, update_cache_for_swarm},
monitor::{refresh_server_cache, refresh_swarm_cache},
resource::{self, setup_deployment_execution},
state::action_states,
};
@@ -54,15 +55,15 @@ impl Resolve<ExecuteArgs> for BatchDeploy {
"BatchDeploy",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeploy>(&self.pattern, user)
.await?,
@@ -75,7 +76,7 @@ impl Resolve<ExecuteArgs> for Deploy {
"Deploy",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -85,8 +86,12 @@ impl Resolve<ExecuteArgs> for Deploy {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut deployment, swarm_or_server) =
setup_deployment_execution(
&self.deployment,
@@ -95,6 +100,8 @@ impl Resolve<ExecuteArgs> for Deploy {
)
.await?;
swarm_or_server.verify_has_target()?;
// get the action state for the deployment (or insert default).
let action_state = action_states()
.deployment
@@ -108,7 +115,7 @@ impl Resolve<ExecuteArgs> for Deploy {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
// This block resolves the attached Build to an actual versioned image
@@ -203,7 +210,10 @@ impl Resolve<ExecuteArgs> for Deploy {
update.version = version;
update_update(update.clone()).await?;
let deployment_id = deployment.id.clone();
match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
match swarm_request(
&swarm.config.server_ids,
@@ -216,7 +226,7 @@ impl Resolve<ExecuteArgs> for Deploy {
.await
{
Ok(logs) => {
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
update.logs.extend(logs)
}
Err(e) => {
@@ -240,7 +250,7 @@ impl Resolve<ExecuteArgs> for Deploy {
.await
{
Ok(log) => {
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.logs.push(log)
}
Err(e) => {
@@ -253,6 +263,20 @@ impl Resolve<ExecuteArgs> for Deploy {
}
}
if let Err(e) = resource::update_info::<Deployment>(
&deployment_id,
&DeploymentInfo {
latest_image_digest: Default::default(),
},
)
.await
{
warn!(
"Failed to update deployment {} info after deploy | {e:#}",
deployment_id
);
}
update.finalize();
update_update(update.clone()).await?;
@@ -381,7 +405,7 @@ pub async fn pull_deployment_inner(
Err(e) => Log::error("Pull image", format_serror(&e.into())),
};
update_cache_for_server(server, true).await;
refresh_server_cache(server, true).await;
anyhow::Ok(log)
}
.await;
@@ -398,7 +422,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
"PullDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -406,8 +430,12 @@ impl Resolve<ExecuteArgs> for PullDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -434,7 +462,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
action_state.update(|state| state.pulling = true)?;
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = pull_deployment_inner(deployment, &server).await?;
@@ -452,7 +480,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
"StartDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -460,8 +488,12 @@ impl Resolve<ExecuteArgs> for StartDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -489,7 +521,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match periphery_client(&server)
@@ -507,7 +539,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -520,7 +552,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
"RestartDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -528,8 +560,12 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -557,7 +593,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match periphery_client(&server)
@@ -577,7 +613,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -590,7 +626,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
"PauseDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -598,8 +634,12 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -627,7 +667,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match periphery_client(&server)
@@ -645,7 +685,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -658,7 +698,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
"UnpauseDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -666,8 +706,12 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -695,7 +739,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match periphery_client(&server)
@@ -715,7 +759,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -728,7 +772,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
"StopDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -738,8 +782,12 @@ impl Resolve<ExecuteArgs> for StopDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -767,7 +815,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match periphery_client(&server)
@@ -793,7 +841,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -817,15 +865,15 @@ impl Resolve<ExecuteArgs> for BatchDestroyDeployment {
"BatchDestroyDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDestroyDeployment>(
&self.pattern,
@@ -841,7 +889,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
"DestroyDeployment",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
deployment = self.deployment,
@@ -851,8 +899,12 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
@@ -860,6 +912,8 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
)
.await?;
swarm_or_server.verify_has_target()?;
// get the action state for the deployment (or insert default).
let action_state = action_states()
.deployment
@@ -873,10 +927,11 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let log = match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
match swarm_request(
&swarm.config.server_ids,
@@ -887,7 +942,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
.await
{
Ok(log) => {
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
log
}
Err(e) => Log::error(
@@ -915,7 +970,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
.await
{
Ok(log) => {
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
log
}
Err(e) => Log::error(
+175 -92
View File
@@ -14,26 +14,32 @@ use komodo_client::{
RotateAllServerKeys, RotateCoreKeys,
},
entities::{
deployment::DeploymentState, server::ServerState,
stack::StackState,
SwarmOrServer, deployment::DeploymentState, server::ServerState,
stack::StackState, swarm::SwarmState,
},
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use periphery_client::api;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use tokio::sync::Mutex;
use crate::{
api::execute::{
ExecuteArgs, pull_deployment_inner, pull_stack_inner,
api::{
execute::ExecuteArgs,
write::{
check_deployment_for_update_inner, check_stack_for_update_inner,
},
},
config::{core_config, core_keys},
helpers::{periphery_client, update::update_update},
helpers::{
periphery_client, query::find_swarm_or_server,
update::update_update,
},
resource::rotate_server_keys,
state::{
db_client, deployment_status_cache, server_status_cache,
stack_status_cache,
stack_status_cache, swarm_status_cache,
},
};
@@ -48,14 +54,18 @@ impl Resolve<ExecuteArgs> for ClearRepoCache {
"ClearRepoCache",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id
)
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -127,14 +137,18 @@ impl Resolve<ExecuteArgs> for BackupCoreDatabase {
"BackupCoreDatabase",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -180,14 +194,18 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
"GlobalAutoUpdate",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -209,6 +227,9 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
let servers = find_collect(&db_client().servers, None, None)
.await
.context("Failed to query for servers from database")?;
let swarms = find_collect(&db_client().swarms, None, None)
.await
.context("Failed to query for swarms from database")?;
let query = doc! {
"$or": [
@@ -217,13 +238,13 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
]
};
let (stacks, repos) = tokio::try_join!(
find_collect(&db_client().stacks, query.clone(), None),
find_collect(&db_client().repos, None, None)
)
.context("Failed to query for resources from database")?;
let stacks =
find_collect(&db_client().stacks, query.clone(), None)
.await
.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]
@@ -234,54 +255,71 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
else {
continue;
};
// Only pull running stacks.
// Only check running stacks.
if !matches!(status.curr.state, StackState::Running) {
continue;
}
if let Some(server) =
servers.iter().find(|s| s.id == stack.config.server_id)
// This check is probably redundant along with running check
// but shouldn't hurt
&& server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
let name = stack.name.clone();
let repo = if stack.config.linked_repo.is_empty() {
None
} else {
let Some(repo) =
repos.iter().find(|r| r.id == stack.config.linked_repo)
else {
update.push_error_log(
&format!("Pull Stack {name}"),
format!(
"Did not find any Repo matching {}",
stack.config.linked_repo
),
);
continue;
};
Some(repo.clone())
};
if let Err(e) =
pull_stack_inner(stack, Vec::new(), server, repo, None)
let swarm_or_server = find_swarm_or_server(
&stack.config.swarm_id,
&swarms,
&stack.config.server_id,
&servers,
)?;
// Ensure server / swarm is reachable
match &swarm_or_server {
SwarmOrServer::None => continue,
SwarmOrServer::Server(server) => {
if !server_status_cache
.get(&server.id)
.await
{
update.push_error_log(
&format!("Pull Stack {name}"),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
continue;
}
update.logs[0]
.stdout
.push_str(&format!("Pulled Stack {}", bold(name)));
}
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 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)
));
}
}
@@ -290,43 +328,77 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
find_collect(&db_client().deployments, query, None)
.await
.context("Failed to query for deployments from database")?;
for deployment in deployments {
let Some(status) =
deployment_status_cache.get(&deployment.id).await
else {
continue;
};
// Only pull running deployments.
// Only check running deployments.
if !matches!(status.curr.state, DeploymentState::Running) {
continue;
}
if let Some(server) =
servers.iter().find(|s| s.id == deployment.config.server_id)
// This check is probably redundant along with running check
// but shouldn't hurt
&& server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
let name = deployment.name.clone();
if let Err(e) =
pull_deployment_inner(deployment, server).await
{
update.push_error_log(
&format!("Pull Deployment {name}"),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
let swarm_or_server = find_swarm_or_server(
&deployment.config.swarm_id,
&swarms,
&deployment.config.server_id,
&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;
}
update.logs[0].stdout.push_str(&format!(
"Pulled Deployment {} ✅",
bold(name)
));
}
SwarmOrServer::Swarm(swarm) => {
if !swarm_status_cache
.get(&swarm.id)
.await
.map(|s| {
matches!(
s.state,
SwarmState::Healthy | SwarmState::Unhealthy
)
})
.unwrap_or_default()
{
continue;
}
}
}
let name = deployment.name.clone();
if let Err(e) = check_deployment_for_update_inner(
deployment,
&swarm_or_server,
self.skip_auto_update,
true,
)
.await
{
update.push_error_log(
&format!("Check Deployment {name}"),
format_serror(&e.into()),
);
} else {
if !update.logs[0].stdout.is_empty() {
update.logs[0].stdout.push('\n');
}
update.logs[0]
.stdout
.push_str(&format!("Checked Deployment {}", bold(name)));
}
}
@@ -350,14 +422,18 @@ impl Resolve<ExecuteArgs> for RotateAllServerKeys {
"RotateAllServerKeys",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
)
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -464,7 +540,7 @@ impl Resolve<ExecuteArgs> for RotateCoreKeys {
"RotateCoreKeys",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
force = self.force,
@@ -472,7 +548,11 @@ impl Resolve<ExecuteArgs> for RotateCoreKeys {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
if !user.admin {
return Err(
@@ -523,7 +603,10 @@ impl Resolve<ExecuteArgs> for RotateCoreKeys {
);
}
let public_key = core_keys.rotate().await?.into_inner();
let public_key = core_keys
.rotate(mogh_pki::PkiKind::Mutual)
.await?
.into_inner();
info!("New Public Key: {public_key}");
+66 -37
View File
@@ -6,7 +6,6 @@ use axum::{
};
use axum_extra::{TypedHeader, headers::ContentType};
use database::mungos::by_id::find_one_by_id;
use derive_variants::{EnumVariants, ExtractVariant};
use formatting::format_serror;
use futures_util::future::join_all;
use komodo_client::{
@@ -18,17 +17,18 @@ use komodo_client::{
user::User,
},
};
use resolver_api::Resolve;
use response::JsonString;
use mogh_auth_server::middleware::authenticate_request;
use mogh_error::Json;
use mogh_error::JsonString;
use mogh_resolver::Resolve;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::Json;
use strum::Display;
use strum::{Display, EnumDiscriminants};
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::auth_request,
auth::KomodoAuthImpl,
helpers::update::{init_execution_update, update_update},
resource::{KomodoResource, list_full_for_user_using_pattern},
state::db_client,
@@ -48,26 +48,22 @@ mod sync;
use super::Variant;
pub use {
deployment::pull_deployment_inner, stack::pull_stack_inner,
};
pub struct ExecuteArgs {
/// The execution id.
/// Unique for every /execute call.
pub id: Uuid,
/// The task id.
/// Unique for every '/execute' call.
pub task_id: Uuid,
pub user: User,
pub update: Update,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants,
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[variant_derive(Debug, Display)]
#[strum_discriminants(name(ExecuteRequestMethod), derive(Display))]
#[args(ExecuteArgs)]
#[response(JsonString)]
#[error(serror::Error)]
#[error(mogh_error::Error)]
#[serde(tag = "type", content = "params")]
pub enum ExecuteRequest {
// ==== STACK ====
@@ -152,6 +148,7 @@ pub enum ExecuteRequest {
// ==== SWARM ====
RemoveSwarmNodes(RemoveSwarmNodes),
UpdateSwarmNode(UpdateSwarmNode),
RemoveSwarmStacks(RemoveSwarmStacks),
RemoveSwarmServices(RemoveSwarmServices),
CreateSwarmConfig(CreateSwarmConfig),
@@ -173,14 +170,16 @@ pub fn router() -> Router {
Router::new()
.route("/", post(handler))
.route("/{variant}", post(variant_handler))
.layer(middleware::from_fn(auth_request))
.layer(middleware::from_fn(
authenticate_request::<KomodoAuthImpl, true>,
))
}
async fn variant_handler(
user: Extension<User>,
Path(Variant { variant }): Path<Variant>,
Json(params): Json<serde_json::Value>,
) -> serror::Result<(TypedHeader<ContentType>, String)> {
) -> mogh_error::Result<(TypedHeader<ContentType>, String)> {
let req: ExecuteRequest = serde_json::from_value(json!({
"type": variant,
"params": params,
@@ -191,7 +190,7 @@ async fn variant_handler(
async fn handler(
Extension(user): Extension<User>,
Json(request): Json<ExecuteRequest>,
) -> serror::Result<(TypedHeader<ContentType>, String)> {
) -> mogh_error::Result<(TypedHeader<ContentType>, String)> {
let res = match inner_handler(request, user).await? {
ExecutionResult::Single(update) => serde_json::to_string(&update)
.context("Failed to serialize Update")?,
@@ -251,11 +250,19 @@ pub fn inner_handler(
async move {
let log = match handle.await {
Ok(Err(e)) => {
warn!("/execute request {task_id} task error: {e:#}",);
warn!(
api = "Execute",
task_id = task_id.to_string(),
"/execute request task error: {e:#}",
);
Log::error("Task Error", format_serror(&e.into()))
}
Err(e) => {
warn!("/execute request {task_id} spawn error: {e:?}",);
warn!(
api = "Execute",
task_id = task_id.to_string(),
"/execute request spawn error: {e:?}",
);
Log::error("Spawn Error", format!("{e:#?}"))
}
_ => return,
@@ -269,8 +276,8 @@ pub fn inner_handler(
let mut update =
find_one_by_id(&db_client().updates, &update_id)
.await
.context("failed to query to db")?
.context("no update exists with given id")?;
.context("Failed to query to db")?
.context("No Update exists with given id")?;
update.logs.push(log);
update.finalize();
update_update(update).await
@@ -279,7 +286,10 @@ pub fn inner_handler(
if let Err(e) = res {
warn!(
"failed to update update with task error log | {e:#}"
api = "Execute",
task_id = task_id.to_string(),
update_id,
"Failed to modify Update with task error log | {e:#}"
);
}
}
@@ -295,25 +305,44 @@ async fn task(
user: User,
update: Update,
) -> anyhow::Result<String> {
let variant = request.extract_variant();
let method: ExecuteRequestMethod = (&request).into();
let user_id = user.id.clone();
let username = user.username.clone();
info!(
"/execute request {id} | {variant} | user: {}",
user.username
api = "Execute",
task_id = id.to_string(),
method = method.to_string(),
user_id,
username,
"EXECUTE REQUEST",
);
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),
};
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),
};
if let Err(e) = &res {
warn!("/execute request {id} error: {e:#}");
warn!(
api = "Execute",
task_id = id.to_string(),
method = method.to_string(),
user_id,
username,
"EXECUTE REQUEST | ERROR: {e:#}"
);
}
res
+9 -5
View File
@@ -17,7 +17,7 @@ use komodo_client::{
user::User,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use tokio::sync::Mutex;
use crate::{
@@ -46,7 +46,7 @@ impl Resolve<ExecuteArgs> for BatchRunProcedure {
async fn resolve(
self,
ExecuteArgs { user, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchRunProcedure>(&self.pattern, user)
.await?,
@@ -59,7 +59,7 @@ impl Resolve<ExecuteArgs> for RunProcedure {
"RunProcedure",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
procedure = self.procedure,
@@ -67,8 +67,12 @@ impl Resolve<ExecuteArgs> for RunProcedure {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
Ok(
resolve_inner(self.procedure, user.clone(), update.clone())
.await?,
+40 -24
View File
@@ -22,8 +22,8 @@ use komodo_client::{
update::{Log, Update},
},
};
use mogh_resolver::Resolve;
use periphery_client::api;
use resolver_api::Resolve;
use tokio_util::sync::CancellationToken;
use crate::{
@@ -55,15 +55,15 @@ impl Resolve<ExecuteArgs> for BatchCloneRepo {
"BatchCloneRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchCloneRepo>(&self.pattern, user)
.await?,
@@ -76,7 +76,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
"CloneRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -84,8 +84,12 @@ impl Resolve<ExecuteArgs> for CloneRepo {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
@@ -186,15 +190,15 @@ impl Resolve<ExecuteArgs> for BatchPullRepo {
"BatchPullRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchPullRepo>(&self.pattern, user)
.await?,
@@ -207,7 +211,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
"PullRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -215,8 +219,12 @@ impl Resolve<ExecuteArgs> for PullRepo {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
@@ -316,7 +324,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
)]
async fn handle_repo_update_return(
update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
// The Err case of to_document should be unreachable,
@@ -363,15 +371,15 @@ impl Resolve<ExecuteArgs> for BatchBuildRepo {
"BatchBuildRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchBuildRepo>(&self.pattern, user)
.await?,
@@ -384,7 +392,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
"BuildRepo",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -392,8 +400,12 @@ impl Resolve<ExecuteArgs> for BuildRepo {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let mut repo = get_check_permissions::<Repo>(
&self.repo,
user,
@@ -614,7 +626,7 @@ async fn handle_builder_early_return(
repo_id: String,
repo_name: String,
is_cancel: bool,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
update.finalize();
// Need to manually update the update before cache refresh,
// and before broadcast with add_update.
@@ -705,7 +717,7 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
"CancelRepoBuild",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
repo = self.repo,
@@ -713,8 +725,12 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
+176 -92
View File
@@ -9,12 +9,12 @@ use komodo_client::{
update::{Log, Update},
},
};
use mogh_resolver::Resolve;
use periphery_client::api;
use resolver_api::Resolve;
use crate::{
helpers::{periphery_client, update::update_update},
monitor::update_cache_for_server,
monitor::refresh_server_cache,
permission::get_check_permissions,
state::action_states,
};
@@ -26,7 +26,7 @@ impl Resolve<ExecuteArgs> for StartContainer {
"StartContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -35,8 +35,12 @@ impl Resolve<ExecuteArgs> for StartContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -57,7 +61,7 @@ impl Resolve<ExecuteArgs> for StartContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -76,7 +80,7 @@ impl Resolve<ExecuteArgs> for StartContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -90,7 +94,7 @@ impl Resolve<ExecuteArgs> for RestartContainer {
"RestartContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -99,8 +103,12 @@ impl Resolve<ExecuteArgs> for RestartContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -121,7 +129,7 @@ impl Resolve<ExecuteArgs> for RestartContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -142,7 +150,7 @@ impl Resolve<ExecuteArgs> for RestartContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -156,7 +164,7 @@ impl Resolve<ExecuteArgs> for PauseContainer {
"PauseContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -165,8 +173,12 @@ impl Resolve<ExecuteArgs> for PauseContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -187,7 +199,7 @@ impl Resolve<ExecuteArgs> for PauseContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -206,7 +218,7 @@ impl Resolve<ExecuteArgs> for PauseContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -220,7 +232,7 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
"UnpauseContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -229,8 +241,12 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -251,7 +267,7 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -272,7 +288,7 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -286,7 +302,7 @@ impl Resolve<ExecuteArgs> for StopContainer {
"StopContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -297,8 +313,12 @@ impl Resolve<ExecuteArgs> for StopContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -319,7 +339,7 @@ impl Resolve<ExecuteArgs> for StopContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -340,7 +360,7 @@ impl Resolve<ExecuteArgs> for StopContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -354,7 +374,7 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
"DestroyContainer",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -365,8 +385,12 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let DestroyContainer {
server,
container,
@@ -393,7 +417,7 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
let mut update = update.clone();
// Send update after setting action state, this way frontend gets correct state.
// Send update after setting action state, this way UI gets correct state.
update_update(update.clone()).await?;
let periphery = periphery_client(&server).await?;
@@ -416,7 +440,7 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -430,7 +454,7 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
"StartAllContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -438,8 +462,12 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -477,7 +505,7 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
);
}
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -490,7 +518,7 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
"RestartAllContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -498,8 +526,12 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -539,7 +571,7 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
);
}
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -552,7 +584,7 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
"PauseAllContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -560,8 +592,12 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -599,7 +635,7 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
);
}
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -612,7 +648,7 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
"UnpauseAllContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -620,8 +656,12 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -661,7 +701,7 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
);
}
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -674,7 +714,7 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
"StopAllContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -682,8 +722,12 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -721,7 +765,7 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
);
}
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -734,7 +778,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
"PruneContainers",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -742,8 +786,12 @@ impl Resolve<ExecuteArgs> for PruneContainers {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -785,7 +833,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -799,7 +847,7 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
"DeleteNetwork",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -808,8 +856,12 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -846,7 +898,7 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -860,7 +912,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
"PruneNetworks",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -868,8 +920,12 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -909,7 +965,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -923,7 +979,7 @@ impl Resolve<ExecuteArgs> for DeleteImage {
"DeleteImage",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -932,8 +988,12 @@ impl Resolve<ExecuteArgs> for DeleteImage {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -967,7 +1027,7 @@ impl Resolve<ExecuteArgs> for DeleteImage {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -981,7 +1041,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
"PruneImages",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -989,8 +1049,12 @@ impl Resolve<ExecuteArgs> for PruneImages {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1028,7 +1092,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1042,7 +1106,7 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
"DeleteVolume",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1051,8 +1115,12 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1089,7 +1157,7 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1103,7 +1171,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
"PruneVolumes",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1111,8 +1179,12 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1150,7 +1222,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1164,7 +1236,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
"PruneDockerBuilders",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1172,8 +1244,12 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1211,7 +1287,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1225,7 +1301,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
"PruneBuildx",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1233,8 +1309,12 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1272,7 +1352,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1286,7 +1366,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
"PruneSystem",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
server = self.server,
@@ -1294,8 +1374,12 @@ impl Resolve<ExecuteArgs> for PruneSystem {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -1332,7 +1416,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
};
update.logs.push(log);
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
update.finalize();
update_update(update.clone()).await?;
+102 -55
View File
@@ -20,12 +20,12 @@ use komodo_client::{
user::User,
},
};
use mogh_error::AddStatusCodeError as _;
use mogh_resolver::Resolve;
use periphery_client::api::{
DeployStackResponse, compose::*, swarm::DeploySwarmStack,
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError as _;
use uuid::Uuid;
use crate::{
@@ -39,7 +39,7 @@ use crate::{
add_update_without_send, init_execution_update, update_update,
},
},
monitor::{update_cache_for_server, update_cache_for_swarm},
monitor::{refresh_server_cache, refresh_swarm_cache},
permission::get_check_permissions,
resource,
stack::{
@@ -69,15 +69,15 @@ impl Resolve<ExecuteArgs> for BatchDeployStack {
"BatchDeployStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeployStack>(&self.pattern, user)
.await?,
@@ -90,7 +90,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
"DeployStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -100,8 +100,12 @@ impl Resolve<ExecuteArgs> for DeployStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
@@ -109,6 +113,8 @@ impl Resolve<ExecuteArgs> for DeployStack {
)
.await?;
swarm_or_server.verify_has_target()?;
let mut repo = if !stack.config.files_on_host
&& !stack.config.linked_repo.is_empty()
{
@@ -185,6 +191,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
commit_hash,
commit_message,
} = match &swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
swarm_request(
&swarm.config.server_ids,
@@ -308,11 +315,12 @@ impl Resolve<ExecuteArgs> for DeployStack {
// Ensure cached stack state up to date by updating server cache
match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
SwarmOrServer::Server(server) => {
update_cache_for_server(&server, true).await;
refresh_server_cache(&server, true).await;
}
}
@@ -338,15 +346,15 @@ impl Resolve<ExecuteArgs> for BatchDeployStackIfChanged {
"BatchDeployStackIfChanged",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchDeployStackIfChanged>(
&self.pattern,
@@ -362,7 +370,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
"DeployStackIfChanged",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -371,8 +379,12 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
@@ -429,7 +441,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
.resolve(&ExecuteArgs {
user: user.clone(),
update,
id: *id,
task_id: *task_id,
})
.await
}
@@ -551,7 +563,7 @@ async fn deploy_services(
stack: String,
services: Vec<String>,
user: &User,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
// The existing update is initialized to DeployStack,
// but also has not been created on database.
// Setup a new update here.
@@ -568,7 +580,7 @@ async fn deploy_services(
.resolve(&ExecuteArgs {
user: user.clone(),
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
}
@@ -585,7 +597,7 @@ async fn restart_services(
stack: String,
services: Vec<String>,
user: &User,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
// The existing update is initialized to DeployStack,
// but also has not been created on database.
// Setup a new update here.
@@ -599,7 +611,7 @@ async fn restart_services(
.resolve(&ExecuteArgs {
user: user.clone(),
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
}
@@ -762,15 +774,15 @@ impl Resolve<ExecuteArgs> for BatchPullStack {
"BatchPullStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
Ok(
super::batch_execute::<BatchPullStack>(&self.pattern, user)
.await?,
@@ -874,7 +886,7 @@ pub async fn pull_stack_inner(
.await?;
// Ensure cached stack state up to date by updating server cache
update_cache_for_server(server, true).await;
refresh_server_cache(server, true).await;
Ok(res)
}
@@ -884,7 +896,7 @@ impl Resolve<ExecuteArgs> for PullStack {
"PullStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -893,8 +905,12 @@ impl Resolve<ExecuteArgs> for PullStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
@@ -955,7 +971,7 @@ impl Resolve<ExecuteArgs> for StartStack {
"StartStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -964,8 +980,12 @@ impl Resolve<ExecuteArgs> for StartStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<StartStack>(
&self.stack,
self.services,
@@ -984,7 +1004,7 @@ impl Resolve<ExecuteArgs> for RestartStack {
"RestartStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -993,8 +1013,12 @@ impl Resolve<ExecuteArgs> for RestartStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<RestartStack>(
&self.stack,
self.services,
@@ -1015,7 +1039,7 @@ impl Resolve<ExecuteArgs> for PauseStack {
"PauseStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1024,8 +1048,12 @@ impl Resolve<ExecuteArgs> for PauseStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<PauseStack>(
&self.stack,
self.services,
@@ -1044,7 +1072,7 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
"UnpauseStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1053,8 +1081,12 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<UnpauseStack>(
&self.stack,
self.services,
@@ -1073,7 +1105,7 @@ impl Resolve<ExecuteArgs> for StopStack {
"StopStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1082,8 +1114,12 @@ impl Resolve<ExecuteArgs> for StopStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
execute_compose::<StopStack>(
&self.stack,
self.services,
@@ -1114,15 +1150,15 @@ impl Resolve<ExecuteArgs> for BatchDestroyStack {
"BatchDestroyStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
pattern = self.pattern,
)
)]
async fn resolve(
self,
ExecuteArgs { user, id, .. }: &ExecuteArgs,
) -> serror::Result<BatchExecutionResponse> {
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
) -> mogh_error::Result<BatchExecutionResponse> {
super::batch_execute::<BatchDestroyStack>(&self.pattern, user)
.await
.map_err(Into::into)
@@ -1134,7 +1170,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
"DestroyStack",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1145,8 +1181,12 @@ impl Resolve<ExecuteArgs> for DestroyStack {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
@@ -1154,7 +1194,10 @@ impl Resolve<ExecuteArgs> for DestroyStack {
)
.await?;
swarm_or_server.verify_has_target()?;
match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
if !self.services.is_empty() {
return Err(
@@ -1175,7 +1218,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
action_state.update(|state| state.destroying = true)?;
let mut update = update.clone();
// Send update here for frontend to recheck action state
// Send update here for UI to recheck action state
update_update(update.clone()).await?;
match swarm_request(
@@ -1198,7 +1241,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
}
// Ensure cached stack state up to date by updating swarm cache
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
update.finalize();
update_update(update.clone()).await?;
@@ -1226,7 +1269,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
"RunStackService",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
stack = self.stack,
@@ -1236,8 +1279,12 @@ impl Resolve<ExecuteArgs> for RunStackService {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let (mut stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
+132 -34
View File
@@ -1,18 +1,14 @@
use formatting::format_serror;
use komodo_client::{
api::execute::{
CreateSwarmConfig, CreateSwarmSecret, RemoveSwarmConfigs,
RemoveSwarmNodes, RemoveSwarmSecrets, RemoveSwarmServices,
RemoveSwarmStacks, RotateSwarmConfig, RotateSwarmSecret,
},
api::execute::*,
entities::{permission::PermissionLevel, swarm::Swarm},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
api::execute::ExecuteArgs,
helpers::{swarm::swarm_request, update::update_update},
monitor::update_cache_for_swarm,
monitor::refresh_swarm_cache,
permission::get_check_permissions,
};
@@ -21,7 +17,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
"RemoveSwarmNodes",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -31,7 +27,11 @@ impl Resolve<ExecuteArgs> for RemoveSwarmNodes {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_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);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Nodes",
@@ -72,12 +72,78 @@ 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(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -87,7 +153,11 @@ impl Resolve<ExecuteArgs> for RemoveSwarmStacks {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -111,7 +181,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmStacks {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Stacks",
@@ -133,7 +203,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
"RemoveSwarmServices",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -142,7 +212,11 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -165,7 +239,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmServices {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Services",
@@ -187,7 +261,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
"CreateSwarmConfig",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -196,7 +270,11 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -222,7 +300,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmConfig {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Create Swarm Config",
@@ -244,7 +322,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
"RotateSwarmConfig",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -253,7 +331,11 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -277,7 +359,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmConfig {
{
Ok(logs) => {
update.logs.extend(logs);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Rotate Swarm Config",
@@ -299,7 +381,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
"RemoveSwarmConfigs",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -308,7 +390,11 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -331,7 +417,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmConfigs {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Configs",
@@ -353,7 +439,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
"CreateSwarmSecret",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -362,7 +448,11 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -389,7 +479,7 @@ impl Resolve<ExecuteArgs> for CreateSwarmSecret {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Create Swarm Secret",
@@ -411,7 +501,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
"RotateSwarmSecret",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -420,7 +510,11 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -444,7 +538,7 @@ impl Resolve<ExecuteArgs> for RotateSwarmSecret {
{
Ok(logs) => {
update.logs.extend(logs);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Rotate Swarm Secret",
@@ -466,7 +560,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
"RemoveSwarmSecrets",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
swarm = self.swarm,
@@ -475,7 +569,11 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> Result<Self::Response, Self::Error> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
@@ -498,7 +596,7 @@ impl Resolve<ExecuteArgs> for RemoveSwarmSecrets {
{
Ok(log) => {
update.logs.push(log);
update_cache_for_swarm(&swarm, true).await;
refresh_swarm_cache(&swarm, true).await;
}
Err(e) => update.push_error_log(
"Remove Swarm Secrets",
+81 -184
View File
@@ -21,12 +21,13 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
swarm::Swarm,
sync::ResourceSync,
update::{Log, Update},
user::sync_user,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
api::write::WriteArgs,
@@ -53,7 +54,7 @@ impl Resolve<ExecuteArgs> for RunSync {
"RunSync",
skip_all,
fields(
id = id.to_string(),
task_id = task_id.to_string(),
operator = user.id,
update_id = update.id,
sync = self.sync,
@@ -63,8 +64,12 @@ impl Resolve<ExecuteArgs> for RunSync {
)]
async fn resolve(
self,
ExecuteArgs { user, update, id }: &ExecuteArgs,
) -> serror::Result<Update> {
ExecuteArgs {
user,
update,
task_id,
}: &ExecuteArgs,
) -> mogh_error::Result<Update> {
let RunSync {
sync,
resource_type: match_resource_type,
@@ -134,56 +139,36 @@ impl Resolve<ExecuteArgs> for RunSync {
let Some(resource_type) = match_resource_type else {
return 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),
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),
}
};
}
// 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<_>>()
});
@@ -231,136 +216,39 @@ impl Resolve<ExecuteArgs> for RunSync {
let delete = sync.config.managed || sync.config.delete;
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()
};
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 (
variables_to_create,
@@ -398,6 +286,7 @@ impl Resolve<ExecuteArgs> for RunSync {
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()
@@ -425,7 +314,11 @@ impl Resolve<ExecuteArgs> for RunSync {
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(
@@ -464,6 +357,10 @@ impl Resolve<ExecuteArgs> for RunSync {
);
// Dependent on server
maybe_extend(
&mut update.logs,
Swarm::execute_sync_updates(swarm_deltas).await,
);
maybe_extend(
&mut update.logs,
Builder::execute_sync_updates(builder_deltas).await,
+1 -1
View File
@@ -2,8 +2,8 @@ use std::sync::Arc;
use anyhow::anyhow;
use axum::{Router, http::HeaderMap};
use cache::CloneCache;
use komodo_client::entities::resource::Resource;
use mogh_cache::CloneCache;
use tokio::sync::Mutex;
use crate::resource::KomodoResource;
+13 -13
View File
@@ -11,7 +11,7 @@ use komodo_client::{
stack::Stack, sync::ResourceSync, user::git_webhook_user,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use serde::Deserialize;
use serde_json::json;
use uuid::Uuid;
@@ -79,7 +79,7 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -121,7 +121,7 @@ impl RepoExecution for CloneRepo {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -145,7 +145,7 @@ impl RepoExecution for PullRepo {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -169,7 +169,7 @@ impl RepoExecution for BuildRepo {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -241,11 +241,11 @@ fn stack_locks() -> &'static ListenerLockCache {
}
pub trait StackExecution {
async fn resolve(stack: Stack) -> serror::Result<()>;
async fn resolve(stack: Stack) -> mogh_error::Result<()>;
}
impl StackExecution for RefreshStackCache {
async fn resolve(stack: Stack) -> serror::Result<()> {
async fn resolve(stack: Stack) -> mogh_error::Result<()> {
RefreshStackCache { stack: stack.id }
.resolve(&WriteArgs {
user: git_webhook_user().to_owned(),
@@ -256,7 +256,7 @@ impl StackExecution for RefreshStackCache {
}
impl StackExecution for DeployStack {
async fn resolve(stack: Stack) -> serror::Result<()> {
async fn resolve(stack: Stack) -> mogh_error::Result<()> {
let user = git_webhook_user().to_owned();
if stack.config.webhook_force_deploy {
let req = ExecuteRequest::DeployStack(DeployStack {
@@ -272,7 +272,7 @@ impl StackExecution for DeployStack {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -290,7 +290,7 @@ impl StackExecution for DeployStack {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -403,7 +403,7 @@ impl SyncExecution for RunSync {
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -517,7 +517,7 @@ pub async fn handle_procedure_webhook<B: super::ExtractBranch>(
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
@@ -583,7 +583,7 @@ pub async fn handle_action_webhook<B: super::ExtractBranch>(
.resolve(&ExecuteArgs {
user,
update,
id: Uuid::new_v4(),
task_id: Uuid::new_v4(),
})
.await
.map_err(|e| e.error)?;
+50 -35
View File
@@ -1,22 +1,18 @@
use std::net::{IpAddr, SocketAddr};
use std::net::IpAddr;
use axum::{
Router,
extract::{ConnectInfo, Path},
http::HeaderMap,
routing::post,
};
use axum::{Router, extract::Path, http::HeaderMap, routing::post};
use komodo_client::entities::{
action::Action, build::Build, procedure::Procedure, repo::Repo,
resource::Resource, stack::Stack, sync::ResourceSync,
};
use rate_limit::WithFailureRateLimit;
use mogh_auth_server::request_ip::RequestIp;
use mogh_error::AddStatusCode;
use mogh_rate_limit::WithFailureRateLimit;
use reqwest::StatusCode;
use serde::Deserialize;
use serror::AddStatusCode;
use tracing::Instrument;
use crate::{resource::KomodoResource, state::auth_rate_limiter};
use crate::{auth::GENERAL_RATE_LIMITER, resource::KomodoResource};
use super::{
CustomSecret, ExtractBranch, VerifySecret,
@@ -55,9 +51,9 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.route(
"/build/{id}",
post(
|Path(Id { id }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(Id { id }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let build =
auth_webhook::<P, Build>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, Build>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("BuildWebhook", id);
async {
@@ -74,16 +70,16 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
.route(
"/repo/{id}/{option}",
post(
|Path(IdAndOption::<RepoWebhookOption> { id, option }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(IdAndOption::<RepoWebhookOption> { id, option }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let repo =
auth_webhook::<P, Repo>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, Repo>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("RepoWebhook", id);
async {
@@ -100,16 +96,16 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
.route(
"/stack/{id}/{option}",
post(
|Path(IdAndOption::<StackWebhookOption> { id, option }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(IdAndOption::<StackWebhookOption> { id, option }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let stack =
auth_webhook::<P, Stack>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, Stack>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("StackWebhook", id);
async {
@@ -126,16 +122,16 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
.route(
"/sync/{id}/{option}",
post(
|Path(IdAndOption::<SyncWebhookOption> { id, option }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(IdAndOption::<SyncWebhookOption> { id, option }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let sync =
auth_webhook::<P, ResourceSync>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, ResourceSync>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("ResourceSyncWebhook", id);
async {
@@ -152,16 +148,16 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
.route(
"/procedure/{id}/{branch}",
post(
|Path(IdAndBranch { id, branch }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(IdAndBranch { id, branch }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let procedure =
auth_webhook::<P, Procedure>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, Procedure>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("ProcedureWebhook", id);
async {
@@ -178,16 +174,16 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
.route(
"/action/{id}/{branch}",
post(
|Path(IdAndBranch { id, branch }), headers: HeaderMap, ConnectInfo(info): ConnectInfo<SocketAddr>, body: String| async move {
|Path(IdAndBranch { id, branch }), RequestIp(ip), headers: HeaderMap, body: String| async move {
let action =
auth_webhook::<P, Action>(&id, &headers, info.ip(), &body).await?;
auth_webhook::<P, Action>(&id, &headers, ip, &body).await?;
tokio::spawn(async move {
let span = info_span!("ActionWebhook", id);
async {
@@ -204,7 +200,7 @@ pub fn router<P: VerifySecret + ExtractBranch>() -> Router {
.instrument(span)
.await
});
serror::Result::Ok(())
mogh_error::Result::Ok(())
},
),
)
@@ -215,7 +211,7 @@ async fn auth_webhook<P, R>(
headers: &HeaderMap,
ip: IpAddr,
body: &str,
) -> serror::Result<Resource<R::Config, R::Info>>
) -> mogh_error::Result<Resource<R::Config, R::Info>>
where
P: VerifySecret,
R: KomodoResource + CustomSecret,
@@ -226,12 +222,31 @@ where
.status_code(StatusCode::BAD_REQUEST)?;
P::verify_secret(headers, body, R::custom_secret(&resource))
.status_code(StatusCode::UNAUTHORIZED)?;
serror::Result::Ok(resource)
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_headers(
auth_rate_limiter(),
headers,
Some(ip),
)
.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
);
})
}
+26 -62
View File
@@ -1,29 +1,20 @@
use axum::{
Router,
http::{HeaderName, HeaderValue},
routing::get,
};
use tower_http::{
services::{ServeDir, ServeFile},
set_header::SetResponseHeaderLayer,
};
use tower_sessions::{
Expiry, MemoryStore, SessionManagerLayer,
cookie::{SameSite, time::Duration},
use axum::{Extension, Router, routing::get};
use komodo_client::entities::user::User;
use mogh_auth_server::middleware::authenticate_request;
use mogh_error::Json;
use mogh_server::{
cors::cors_layer, session::memory_session_layer,
ui::serve_static_ui,
};
use crate::{
config::{core_config, core_host, cors_layer},
ts_client,
};
use crate::{auth::KomodoAuthImpl, config::core_config, ts_client};
pub mod auth;
pub mod execute;
pub mod read;
pub mod user;
pub mod write;
mod listener;
mod openapi;
mod terminal;
mod ws;
@@ -34,18 +25,11 @@ struct Variant {
pub fn app() -> Router {
let config = core_config();
// Setup static frontend services
let frontend_path = &config.frontend_path;
let frontend_index =
ServeFile::new(format!("{frontend_path}/index.html"));
let serve_frontend = ServeDir::new(frontend_path)
.not_found_service(frontend_index.clone());
Router::new()
.merge(openapi::serve_docs())
.route("/version", get(|| async { env!("CARGO_PKG_VERSION") }))
.nest("/auth", auth::router())
.nest("/user", user::router())
.nest("/auth", mogh_auth_server::api::router::<KomodoAuthImpl>())
.nest("/user", user_router())
.nest("/read", read::router())
.nest("/write", write::router())
.nest("/execute", execute::router())
@@ -53,41 +37,21 @@ pub fn app() -> Router {
.nest("/listener", listener::router())
.nest("/ws", ws::router())
.nest("/client", ts_client::router())
.layer(memory_session_layer())
.fallback_service(serve_frontend)
.layer(cors_layer())
.layer(SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-content-type-options"),
HeaderValue::from_static("nosniff"),
))
.layer(SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-frame-options"),
HeaderValue::from_static("DENY"),
))
.layer(SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-xss-protection"),
HeaderValue::from_static("1; mode=block"),
))
.layer(SetResponseHeaderLayer::overriding(
HeaderName::from_static("referrer-policy"),
HeaderValue::from_static("strict-origin-when-cross-origin"),
.layer(memory_session_layer(config))
.fallback_service(serve_static_ui(
&config.ui_path,
config.ui_index_force_no_cache,
))
.layer(cors_layer(config))
}
const MEMORY_SESSION_EXPIRY_SECONDS: i64 = 60;
fn memory_session_layer() -> SessionManagerLayer<MemoryStore> {
let config = core_config();
let mut layer = SessionManagerLayer::new(MemoryStore::default())
.with_expiry(Expiry::OnInactivity(Duration::seconds(
MEMORY_SESSION_EXPIRY_SECONDS,
)))
.with_secure(config.host.starts_with("https://"))
// Needs Lax in order for sessions to work
// accross oauth redirects.
.with_same_site(SameSite::Lax);
if let Some(domain) = core_host().and_then(|url| url.domain()) {
layer = layer.with_domain(domain);
}
layer
fn user_router() -> Router {
Router::new()
.route(
"/",
get(|Extension(user): Extension<User>| async { Json(user) }),
)
.layer(axum::middleware::from_fn(
authenticate_request::<KomodoAuthImpl, false>,
))
}
+18
View File
@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<title>Komodo API Docs</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<script id="api-reference" type="application/json">
$spec
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
use komodo_client::openapi::KomodoApi;
use utoipa::OpenApi as _;
use utoipa_scalar::{Scalar, Servable as _};
pub fn serve_docs() -> Scalar<utoipa::openapi::OpenApi> {
Scalar::with_url("/docs", KomodoApi::openapi())
.custom_html(include_str!("docs.html"))
}
+6 -6
View File
@@ -8,7 +8,7 @@ use komodo_client::{
permission::PermissionLevel,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -23,7 +23,7 @@ impl Resolve<ReadArgs> for GetAction {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Action> {
) -> mogh_error::Result<Action> {
Ok(
get_check_permissions::<Action>(
&self.action,
@@ -39,7 +39,7 @@ impl Resolve<ReadArgs> for ListActions {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<ActionListItem>> {
) -> mogh_error::Result<Vec<ActionListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -61,7 +61,7 @@ impl Resolve<ReadArgs> for ListFullActions {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullActionsResponse> {
) -> mogh_error::Result<ListFullActionsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -83,7 +83,7 @@ impl Resolve<ReadArgs> for GetActionActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ActionActionState> {
) -> mogh_error::Result<ActionActionState> {
let action = get_check_permissions::<Action>(
&self.action,
user,
@@ -104,7 +104,7 @@ impl Resolve<ReadArgs> for GetActionsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetActionsSummaryResponse> {
) -> mogh_error::Result<GetActionsSummaryResponse> {
let actions = resource::list_full_for_user::<Action>(
Default::default(),
user,
+3 -3
View File
@@ -10,7 +10,7 @@ use komodo_client::{
},
entities::permission::PermissionLevel,
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
config::core_config,
@@ -28,7 +28,7 @@ impl Resolve<ReadArgs> for ListAlerts {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListAlertsResponse> {
) -> mogh_error::Result<ListAlertsResponse> {
// Alerts
let query = user_resource_target_query(user, self.query)
.await?
@@ -62,7 +62,7 @@ impl Resolve<ReadArgs> for GetAlert {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetAlertResponse> {
) -> mogh_error::Result<GetAlertResponse> {
let alert = find_one_by_id(&db_client().alerts, &self.id)
.await
.context("failed to query db for alert")?
+5 -5
View File
@@ -8,7 +8,7 @@ use komodo_client::{
permission::PermissionLevel,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -23,7 +23,7 @@ impl Resolve<ReadArgs> for GetAlerter {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Alerter> {
) -> mogh_error::Result<Alerter> {
Ok(
get_check_permissions::<Alerter>(
&self.alerter,
@@ -39,7 +39,7 @@ impl Resolve<ReadArgs> for ListAlerters {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<AlerterListItem>> {
) -> mogh_error::Result<Vec<AlerterListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -61,7 +61,7 @@ impl Resolve<ReadArgs> for ListFullAlerters {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullAlertersResponse> {
) -> mogh_error::Result<ListFullAlertersResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -83,7 +83,7 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetAlertersSummaryResponse> {
) -> mogh_error::Result<GetAlertersSummaryResponse> {
let query = match list_resource_ids_for_user::<Alerter>(
None,
user,
+9 -9
View File
@@ -16,7 +16,7 @@ use komodo_client::{
update::UpdateStatus,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -31,7 +31,7 @@ impl Resolve<ReadArgs> for GetBuild {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Build> {
) -> mogh_error::Result<Build> {
Ok(
get_check_permissions::<Build>(
&self.build,
@@ -47,7 +47,7 @@ impl Resolve<ReadArgs> for ListBuilds {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<BuildListItem>> {
) -> mogh_error::Result<Vec<BuildListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -69,7 +69,7 @@ impl Resolve<ReadArgs> for ListFullBuilds {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullBuildsResponse> {
) -> mogh_error::Result<ListFullBuildsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -91,7 +91,7 @@ impl Resolve<ReadArgs> for GetBuildActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<BuildActionState> {
) -> mogh_error::Result<BuildActionState> {
let build = get_check_permissions::<Build>(
&self.build,
user,
@@ -112,7 +112,7 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetBuildsSummaryResponse> {
) -> mogh_error::Result<GetBuildsSummaryResponse> {
let builds = resource::list_full_for_user::<Build>(
Default::default(),
user,
@@ -160,7 +160,7 @@ impl Resolve<ReadArgs> for GetBuildMonthlyStats {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<GetBuildMonthlyStatsResponse> {
) -> mogh_error::Result<GetBuildMonthlyStatsResponse> {
let curr_ts = unix_timestamp_ms() as i64;
let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS;
@@ -216,7 +216,7 @@ impl Resolve<ReadArgs> for ListBuildVersions {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<BuildVersionResponseItem>> {
) -> mogh_error::Result<Vec<BuildVersionResponseItem>> {
let ListBuildVersions {
build,
major,
@@ -273,7 +273,7 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListCommonBuildExtraArgsResponse> {
) -> mogh_error::Result<ListCommonBuildExtraArgsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
+5 -5
View File
@@ -8,7 +8,7 @@ use komodo_client::{
permission::PermissionLevel,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -23,7 +23,7 @@ impl Resolve<ReadArgs> for GetBuilder {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Builder> {
) -> mogh_error::Result<Builder> {
Ok(
get_check_permissions::<Builder>(
&self.builder,
@@ -39,7 +39,7 @@ impl Resolve<ReadArgs> for ListBuilders {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<BuilderListItem>> {
) -> mogh_error::Result<Vec<BuilderListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -61,7 +61,7 @@ impl Resolve<ReadArgs> for ListFullBuilders {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullBuildersResponse> {
) -> mogh_error::Result<ListFullBuildersResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -83,7 +83,7 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetBuildersSummaryResponse> {
) -> mogh_error::Result<GetBuildersSummaryResponse> {
let query = match list_resource_ids_for_user::<Builder>(
None,
user,
+20 -14
View File
@@ -18,10 +18,10 @@ use komodo_client::{
update::Log,
},
};
use mogh_error::AddStatusCodeError as _;
use mogh_resolver::Resolve;
use periphery_client::api::{self, container::InspectContainer};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError as _;
use crate::{
helpers::{
@@ -40,7 +40,7 @@ impl Resolve<ReadArgs> for GetDeployment {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Deployment> {
) -> mogh_error::Result<Deployment> {
Ok(
get_check_permissions::<Deployment>(
&self.deployment,
@@ -56,7 +56,7 @@ impl Resolve<ReadArgs> for ListDeployments {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<DeploymentListItem>> {
) -> mogh_error::Result<Vec<DeploymentListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -86,7 +86,7 @@ impl Resolve<ReadArgs> for ListFullDeployments {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullDeploymentsResponse> {
) -> mogh_error::Result<ListFullDeploymentsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -108,7 +108,7 @@ impl Resolve<ReadArgs> for GetDeploymentContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetDeploymentContainerResponse> {
) -> mogh_error::Result<GetDeploymentContainerResponse> {
let deployment = get_check_permissions::<Deployment>(
&self.deployment,
user,
@@ -133,7 +133,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Log> {
) -> mogh_error::Result<Log> {
let GetDeploymentLog {
deployment,
tail,
@@ -147,7 +147,10 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
)
.await?;
swarm_or_server.verify_has_target()?;
let log = match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLog {
@@ -180,7 +183,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Log> {
) -> mogh_error::Result<Log> {
let SearchDeploymentLog {
deployment,
terms,
@@ -196,7 +199,10 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
)
.await?;
swarm_or_server.verify_has_target()?;
let log = match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => swarm_request(
&swarm.config.server_ids,
periphery_client::api::swarm::GetSwarmServiceLogSearch {
@@ -233,7 +239,7 @@ impl Resolve<ReadArgs> for InspectDeploymentContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
) -> mogh_error::Result<Container> {
let InspectDeploymentContainer { deployment } = self;
let (deployment, swarm_or_server) = setup_deployment_execution(
&deployment,
@@ -280,7 +286,7 @@ impl Resolve<ReadArgs> for InspectDeploymentSwarmService {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SwarmService> {
) -> mogh_error::Result<SwarmService> {
let InspectDeploymentSwarmService { deployment } = self;
let (deployment, swarm_or_server) = setup_deployment_execution(
&deployment,
@@ -314,7 +320,7 @@ impl Resolve<ReadArgs> for GetDeploymentStats {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ContainerStats> {
) -> mogh_error::Result<ContainerStats> {
let Deployment {
name,
config: DeploymentConfig { server_id, .. },
@@ -344,7 +350,7 @@ impl Resolve<ReadArgs> for GetDeploymentActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<DeploymentActionState> {
) -> mogh_error::Result<DeploymentActionState> {
let deployment = get_check_permissions::<Deployment>(
&self.deployment,
user,
@@ -365,7 +371,7 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetDeploymentsSummaryResponse> {
) -> mogh_error::Result<GetDeploymentsSummaryResponse> {
let deployments = resource::list_full_for_user::<Deployment>(
Default::default(),
user,
@@ -408,7 +414,7 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListCommonDeploymentExtraArgsResponse> {
) -> mogh_error::Result<ListCommonDeploymentExtraArgsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
+60 -24
View File
@@ -1,4 +1,4 @@
use std::{collections::HashSet, time::Instant};
use std::collections::HashSet;
use anyhow::{Context, anyhow};
use axum::{
@@ -18,16 +18,19 @@ use komodo_client::{
user::User,
},
};
use resolver_api::Resolve;
use response::Response;
use mogh_auth_server::middleware::authenticate_request;
use mogh_error::Response;
use mogh_error::{AddStatusCodeError, Json};
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::Json;
use strum::{Display, EnumDiscriminants};
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::auth_request,
auth::KomodoAuthImpl,
config::{core_config, core_keys},
helpers::periphery_client,
resource,
@@ -64,10 +67,13 @@ pub struct ReadArgs {
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[strum_discriminants(name(ReadRequestMethod), derive(Display))]
#[args(ReadArgs)]
#[response(Response)]
#[error(serror::Error)]
#[error(mogh_error::Error)]
#[serde(tag = "type", content = "params")]
enum ReadRequest {
GetVersion(GetVersion),
@@ -263,14 +269,16 @@ pub fn router() -> Router {
Router::new()
.route("/", post(handler))
.route("/{variant}", post(variant_handler))
.layer(middleware::from_fn(auth_request))
.layer(middleware::from_fn(
authenticate_request::<KomodoAuthImpl, true>,
))
}
async fn variant_handler(
user: Extension<User>,
Path(Variant { variant }): Path<Variant>,
Json(params): Json<serde_json::Value>,
) -> serror::Result<axum::response::Response> {
) -> mogh_error::Result<axum::response::Response> {
let req: ReadRequest = serde_json::from_value(json!({
"type": variant,
"params": params,
@@ -281,16 +289,34 @@ async fn variant_handler(
async fn handler(
Extension(user): Extension<User>,
Json(request): Json<ReadRequest>,
) -> serror::Result<axum::response::Response> {
let timer = Instant::now();
) -> mogh_error::Result<axum::response::Response> {
let req_id = Uuid::new_v4();
debug!("/read request | user: {}", user.username);
let method: ReadRequestMethod = (&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",
);
let res = request.resolve(&ReadArgs { user }).await;
if let Err(e) = &res {
debug!("/read request {req_id} error: {:#}", e.error);
trace!(
req_id = req_id.to_string(),
method = method.to_string(),
user_id,
username,
"READ REQUEST | ERROR: {:#}",
e.error
);
}
let elapsed = timer.elapsed();
debug!("/read request {req_id} | resolve time: {elapsed:?}");
res.map(|res| res.0)
}
@@ -298,18 +324,20 @@ impl Resolve<ReadArgs> for GetVersion {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<GetVersionResponse> {
) -> mogh_error::Result<GetVersionResponse> {
Ok(GetVersionResponse {
version: env!("CARGO_PKG_VERSION").to_string(),
})
}
}
//
impl Resolve<ReadArgs> for GetCoreInfo {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<GetCoreInfoResponse> {
) -> mogh_error::Result<GetCoreInfoResponse> {
let config = core_config();
let info = GetCoreInfoResponse {
title: config.title.clone(),
@@ -332,11 +360,13 @@ impl Resolve<ReadArgs> for GetCoreInfo {
}
}
//
impl Resolve<ReadArgs> for ListSecrets {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<ListSecretsResponse> {
) -> mogh_error::Result<ListSecretsResponse> {
let mut secrets = core_config()
.secrets
.keys()
@@ -358,7 +388,8 @@ impl Resolve<ReadArgs> for ListSecrets {
}
_ => {
return Err(
anyhow!("target must be `Server` or `Builder`").into(),
anyhow!("target must be `Server` or `Builder`")
.status_code(StatusCode::BAD_REQUEST),
);
}
};
@@ -385,11 +416,13 @@ impl Resolve<ReadArgs> for ListSecrets {
}
}
//
impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListGitProvidersFromConfigResponse> {
) -> mogh_error::Result<ListGitProvidersFromConfigResponse> {
let mut providers = core_config().git_providers.clone();
if let Some(target) = self.target {
@@ -417,7 +450,8 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
}
_ => {
return Err(
anyhow!("target must be `Server` or `Builder`").into(),
anyhow!("target must be `Server` or `Builder`")
.status_code(StatusCode::BAD_REQUEST),
);
}
}
@@ -487,11 +521,13 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
}
}
//
impl Resolve<ReadArgs> for ListDockerRegistriesFromConfig {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<ListDockerRegistriesFromConfigResponse> {
) -> mogh_error::Result<ListDockerRegistriesFromConfigResponse> {
let mut registries = core_config().docker_registries.clone();
if let Some(target) = self.target {
@@ -535,7 +571,7 @@ impl Resolve<ReadArgs> for ListDockerRegistriesFromConfig {
async fn merge_git_providers_for_server(
providers: &mut Vec<GitProvider>,
server_id: &str,
) -> serror::Result<()> {
) -> mogh_error::Result<()> {
let server = resource::get::<Server>(server_id).await?;
let more = periphery_client(&server)
.await?
@@ -574,7 +610,7 @@ fn merge_git_providers(
async fn merge_docker_registries_for_server(
registries: &mut Vec<DockerRegistry>,
server_id: &str,
) -> serror::Result<()> {
) -> mogh_error::Result<()> {
let server = resource::get::<Server>(server_id).await?;
let more = periphery_client(&server)
.await?
+3 -3
View File
@@ -5,9 +5,9 @@ use database::mungos::find::find_collect;
use komodo_client::api::read::{
ListOnboardingKeys, ListOnboardingKeysResponse,
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{api::read::ReadArgs, state::db_client};
@@ -17,7 +17,7 @@ impl Resolve<ReadArgs> for ListOnboardingKeys {
async fn resolve(
self,
ReadArgs { user: admin }: &ReadArgs,
) -> serror::Result<ListOnboardingKeysResponse> {
) -> mogh_error::Result<ListOnboardingKeysResponse> {
if !admin.admin {
return Err(
anyhow!("This call is admin only")
+4 -4
View File
@@ -8,7 +8,7 @@ use komodo_client::{
},
entities::permission::PermissionLevel,
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_user_permission_on_target, state::db_client,
@@ -20,7 +20,7 @@ impl Resolve<ReadArgs> for ListPermissions {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListPermissionsResponse> {
) -> mogh_error::Result<ListPermissionsResponse> {
let res = find_collect(
&db_client().permissions,
doc! {
@@ -39,7 +39,7 @@ impl Resolve<ReadArgs> for GetPermission {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetPermissionResponse> {
) -> mogh_error::Result<GetPermissionResponse> {
if user.admin {
return Ok(PermissionLevel::Write.all());
}
@@ -51,7 +51,7 @@ impl Resolve<ReadArgs> for ListUserTargetPermissions {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListUserTargetPermissionsResponse> {
) -> mogh_error::Result<ListUserTargetPermissionsResponse> {
if !user.admin {
return Err(anyhow!("this method is admin only").into());
}
+6 -6
View File
@@ -6,7 +6,7 @@ use komodo_client::{
procedure::{Procedure, ProcedureState},
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -21,7 +21,7 @@ impl Resolve<ReadArgs> for GetProcedure {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetProcedureResponse> {
) -> mogh_error::Result<GetProcedureResponse> {
Ok(
get_check_permissions::<Procedure>(
&self.procedure,
@@ -37,7 +37,7 @@ impl Resolve<ReadArgs> for ListProcedures {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListProceduresResponse> {
) -> mogh_error::Result<ListProceduresResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -59,7 +59,7 @@ impl Resolve<ReadArgs> for ListFullProcedures {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullProceduresResponse> {
) -> mogh_error::Result<ListFullProceduresResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -81,7 +81,7 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetProceduresSummaryResponse> {
) -> mogh_error::Result<GetProceduresSummaryResponse> {
let procedures = resource::list_full_for_user::<Procedure>(
Default::default(),
user,
@@ -127,7 +127,7 @@ impl Resolve<ReadArgs> for GetProcedureActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetProcedureActionStateResponse> {
) -> mogh_error::Result<GetProcedureActionStateResponse> {
let procedure = get_check_permissions::<Procedure>(
&self.procedure,
user,
+5 -5
View File
@@ -5,7 +5,7 @@ use database::mungos::{
mongodb::options::FindOptions,
};
use komodo_client::api::read::*;
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::state::db_client;
@@ -15,7 +15,7 @@ impl Resolve<ReadArgs> for GetGitProviderAccount {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetGitProviderAccountResponse> {
) -> mogh_error::Result<GetGitProviderAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read git provider accounts").into(),
@@ -35,7 +35,7 @@ impl Resolve<ReadArgs> for ListGitProviderAccounts {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListGitProviderAccountsResponse> {
) -> mogh_error::Result<ListGitProviderAccountsResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read git provider accounts").into(),
@@ -65,7 +65,7 @@ impl Resolve<ReadArgs> for GetDockerRegistryAccount {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetDockerRegistryAccountResponse> {
) -> mogh_error::Result<GetDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read docker registry accounts")
@@ -87,7 +87,7 @@ impl Resolve<ReadArgs> for ListDockerRegistryAccounts {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerRegistryAccountsResponse> {
) -> mogh_error::Result<ListDockerRegistryAccountsResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can read docker registry accounts")
+6 -6
View File
@@ -6,7 +6,7 @@ use komodo_client::{
repo::{Repo, RepoActionState, RepoListItem, RepoState},
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags,
@@ -21,7 +21,7 @@ impl Resolve<ReadArgs> for GetRepo {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Repo> {
) -> mogh_error::Result<Repo> {
Ok(
get_check_permissions::<Repo>(
&self.repo,
@@ -37,7 +37,7 @@ impl Resolve<ReadArgs> for ListRepos {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<RepoListItem>> {
) -> mogh_error::Result<Vec<RepoListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -59,7 +59,7 @@ impl Resolve<ReadArgs> for ListFullRepos {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullReposResponse> {
) -> mogh_error::Result<ListFullReposResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -81,7 +81,7 @@ impl Resolve<ReadArgs> for GetRepoActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<RepoActionState> {
) -> mogh_error::Result<RepoActionState> {
let repo = get_check_permissions::<Repo>(
&self.repo,
user,
@@ -102,7 +102,7 @@ impl Resolve<ReadArgs> for GetReposSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetReposSummaryResponse> {
) -> mogh_error::Result<GetReposSummaryResponse> {
let repos = resource::list_full_for_user::<Repo>(
Default::default(),
user,
+2 -2
View File
@@ -10,7 +10,7 @@ use komodo_client::{
schedule::Schedule,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::{get_all_tags, get_last_run_at},
@@ -24,7 +24,7 @@ impl Resolve<ReadArgs> for ListSchedules {
async fn resolve(
self,
args: &ReadArgs,
) -> serror::Result<Vec<Schedule>> {
) -> mogh_error::Result<Vec<Schedule>> {
let all_tags = get_all_tags(None).await?;
let (actions, procedures) = tokio::try_join!(
list_full_for_user::<Action>(
+28 -28
View File
@@ -35,6 +35,8 @@ use komodo_client::{
update::Log,
},
};
use mogh_error::AddStatusCode;
use mogh_resolver::Resolve;
use periphery_client::api::{
self as periphery,
container::InspectContainer,
@@ -43,8 +45,6 @@ use periphery_client::api::{
},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCode;
use tokio::sync::Mutex;
use crate::{
@@ -61,7 +61,7 @@ impl Resolve<ReadArgs> for GetServersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetServersSummaryResponse> {
) -> mogh_error::Result<GetServersSummaryResponse> {
let servers = resource::list_for_user::<Server>(
Default::default(),
user,
@@ -103,7 +103,7 @@ impl Resolve<ReadArgs> for GetServer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Server> {
) -> mogh_error::Result<Server> {
Ok(
get_check_permissions::<Server>(
&self.server,
@@ -119,7 +119,7 @@ impl Resolve<ReadArgs> for ListServers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<ServerListItem>> {
) -> mogh_error::Result<Vec<ServerListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -141,7 +141,7 @@ impl Resolve<ReadArgs> for ListFullServers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullServersResponse> {
) -> mogh_error::Result<ListFullServersResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -163,7 +163,7 @@ impl Resolve<ReadArgs> for GetServerState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetServerStateResponse> {
) -> mogh_error::Result<GetServerStateResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -185,7 +185,7 @@ impl Resolve<ReadArgs> for GetServerActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ServerActionState> {
) -> mogh_error::Result<ServerActionState> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -206,7 +206,7 @@ impl Resolve<ReadArgs> for GetPeripheryInformation {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetPeripheryInformationResponse> {
) -> mogh_error::Result<GetPeripheryInformationResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -229,7 +229,7 @@ impl Resolve<ReadArgs> for GetSystemInformation {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SystemInformation> {
) -> mogh_error::Result<SystemInformation> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -253,7 +253,7 @@ impl Resolve<ReadArgs> for GetSystemStats {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSystemStatsResponse> {
) -> mogh_error::Result<GetSystemStatsResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -285,7 +285,7 @@ impl Resolve<ReadArgs> for ListSystemProcesses {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSystemProcessesResponse> {
) -> mogh_error::Result<ListSystemProcessesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -320,7 +320,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetHistoricalServerStatsResponse> {
) -> mogh_error::Result<GetHistoricalServerStatsResponse> {
let GetHistoricalServerStats {
server,
granularity,
@@ -373,7 +373,7 @@ impl Resolve<ReadArgs> for ListDockerContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerContainersResponse> {
) -> mogh_error::Result<ListDockerContainersResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -395,7 +395,7 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListAllDockerContainersResponse> {
) -> mogh_error::Result<ListAllDockerContainersResponse> {
let servers = resource::list_for_user::<Server>(
ServerQuery::builder().names(self.servers.clone()).build(),
user,
@@ -432,7 +432,7 @@ impl Resolve<ReadArgs> for GetDockerContainersSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetDockerContainersSummaryResponse> {
) -> mogh_error::Result<GetDockerContainersSummaryResponse> {
let servers = resource::list_full_for_user::<Server>(
Default::default(),
user,
@@ -472,7 +472,7 @@ impl Resolve<ReadArgs> for InspectDockerContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
) -> mogh_error::Result<Container> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -507,7 +507,7 @@ impl Resolve<ReadArgs> for GetContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Log> {
) -> mogh_error::Result<Log> {
let GetContainerLog {
server,
container,
@@ -537,7 +537,7 @@ impl Resolve<ReadArgs> for SearchContainerLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Log> {
) -> mogh_error::Result<Log> {
let SearchContainerLog {
server,
container,
@@ -571,7 +571,7 @@ impl Resolve<ReadArgs> for GetResourceMatchingContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetResourceMatchingContainerResponse> {
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -632,7 +632,7 @@ impl Resolve<ReadArgs> for ListDockerNetworks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerNetworksResponse> {
) -> mogh_error::Result<ListDockerNetworksResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -654,7 +654,7 @@ impl Resolve<ReadArgs> for InspectDockerNetwork {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Network> {
) -> mogh_error::Result<Network> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -685,7 +685,7 @@ impl Resolve<ReadArgs> for ListDockerImages {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerImagesResponse> {
) -> mogh_error::Result<ListDockerImagesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -707,7 +707,7 @@ impl Resolve<ReadArgs> for InspectDockerImage {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Image> {
) -> mogh_error::Result<Image> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -735,7 +735,7 @@ impl Resolve<ReadArgs> for ListDockerImageHistory {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<ImageHistoryResponseItem>> {
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -766,7 +766,7 @@ impl Resolve<ReadArgs> for ListDockerVolumes {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListDockerVolumesResponse> {
) -> mogh_error::Result<ListDockerVolumesResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -788,7 +788,7 @@ impl Resolve<ReadArgs> for InspectDockerVolume {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Volume> {
) -> mogh_error::Result<Volume> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -816,7 +816,7 @@ impl Resolve<ReadArgs> for ListComposeProjects {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListComposeProjectsResponse> {
) -> mogh_error::Result<ListComposeProjectsResponse> {
let server = get_check_permissions::<Server>(
&self.server,
user,
+21 -15
View File
@@ -12,13 +12,13 @@ use komodo_client::{
stack::{Stack, StackActionState, StackListItem, StackState},
},
};
use mogh_error::AddStatusCodeError as _;
use mogh_resolver::Resolve;
use periphery_client::api::{
compose::{GetComposeLog, GetComposeLogSearch},
container::InspectContainer,
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError as _;
use crate::{
helpers::{
@@ -36,7 +36,7 @@ impl Resolve<ReadArgs> for GetStack {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Stack> {
) -> mogh_error::Result<Stack> {
Ok(
get_check_permissions::<Stack>(
&self.stack,
@@ -52,7 +52,7 @@ impl Resolve<ReadArgs> for ListStackServices {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListStackServicesResponse> {
) -> mogh_error::Result<ListStackServicesResponse> {
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
@@ -76,7 +76,7 @@ impl Resolve<ReadArgs> for GetStackLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetStackLogResponse> {
) -> mogh_error::Result<GetStackLogResponse> {
let GetStackLog {
stack,
mut services,
@@ -90,7 +90,10 @@ impl Resolve<ReadArgs> for GetStackLog {
)
.await?;
swarm_or_server.verify_has_target()?;
let log = match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
let service = services.pop().context(
"Must pass single service for Swarm mode Stack logs",
@@ -133,7 +136,7 @@ impl Resolve<ReadArgs> for SearchStackLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SearchStackLogResponse> {
) -> mogh_error::Result<SearchStackLogResponse> {
let SearchStackLog {
stack,
mut services,
@@ -149,7 +152,10 @@ impl Resolve<ReadArgs> for SearchStackLog {
)
.await?;
swarm_or_server.verify_has_target()?;
let log = match swarm_or_server {
SwarmOrServer::None => unreachable!(),
SwarmOrServer::Swarm(swarm) => {
let service = services.pop().context(
"Must pass single service for Swarm mode Stack logs",
@@ -192,7 +198,7 @@ impl Resolve<ReadArgs> for InspectStackContainer {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Container> {
) -> mogh_error::Result<Container> {
let InspectStackContainer { stack, service } = self;
let (stack, swarm_or_server) = setup_stack_execution(
&stack,
@@ -241,7 +247,7 @@ impl Resolve<ReadArgs> for InspectStackSwarmService {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SwarmService> {
) -> mogh_error::Result<SwarmService> {
let InspectStackSwarmService { stack, service } = self;
let (stack, swarm_or_server) = setup_stack_execution(
&stack,
@@ -292,7 +298,7 @@ impl Resolve<ReadArgs> for InspectStackSwarmInfo {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SwarmStack> {
) -> mogh_error::Result<SwarmStack> {
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
@@ -325,7 +331,7 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListCommonStackExtraArgsResponse> {
) -> mogh_error::Result<ListCommonStackExtraArgsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -359,7 +365,7 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListCommonStackBuildExtraArgsResponse> {
) -> mogh_error::Result<ListCommonStackBuildExtraArgsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -393,7 +399,7 @@ impl Resolve<ReadArgs> for ListStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<StackListItem>> {
) -> mogh_error::Result<Vec<StackListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -429,7 +435,7 @@ impl Resolve<ReadArgs> for ListFullStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullStacksResponse> {
) -> mogh_error::Result<ListFullStacksResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -451,7 +457,7 @@ impl Resolve<ReadArgs> for GetStackActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<StackActionState> {
) -> mogh_error::Result<StackActionState> {
let stack = get_check_permissions::<Stack>(
&self.stack,
user,
@@ -472,7 +478,7 @@ impl Resolve<ReadArgs> for GetStacksSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetStacksSummaryResponse> {
) -> mogh_error::Result<GetStacksSummaryResponse> {
let stacks = resource::list_full_for_user::<Stack>(
Default::default(),
user,
+24 -21
View File
@@ -6,7 +6,7 @@ use komodo_client::{
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::{query::get_all_tags, swarm::swarm_request},
@@ -21,7 +21,7 @@ impl Resolve<ReadArgs> for GetSwarm {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Swarm> {
) -> mogh_error::Result<Swarm> {
Ok(
get_check_permissions::<Swarm>(
&self.swarm,
@@ -37,7 +37,7 @@ impl Resolve<ReadArgs> for ListSwarms {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<SwarmListItem>> {
) -> mogh_error::Result<Vec<SwarmListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -59,7 +59,7 @@ impl Resolve<ReadArgs> for ListFullSwarms {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullSwarmsResponse> {
) -> mogh_error::Result<ListFullSwarmsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -81,7 +81,7 @@ impl Resolve<ReadArgs> for GetSwarmActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SwarmActionState> {
) -> mogh_error::Result<SwarmActionState> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -102,7 +102,7 @@ impl Resolve<ReadArgs> for GetSwarmsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSwarmsSummaryResponse> {
) -> mogh_error::Result<GetSwarmsSummaryResponse> {
let swarms = resource::list_full_for_user::<Swarm>(
Default::default(),
user,
@@ -134,6 +134,9 @@ impl Resolve<ReadArgs> for GetSwarmsSummary {
SwarmState::Unhealthy => {
res.unhealthy += 1;
}
SwarmState::Down => {
res.down += 1;
}
}
}
@@ -145,7 +148,7 @@ impl Resolve<ReadArgs> for InspectSwarm {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmResponse> {
) -> mogh_error::Result<InspectSwarmResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -167,7 +170,7 @@ impl Resolve<ReadArgs> for ListSwarmNodes {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmNodesResponse> {
) -> mogh_error::Result<ListSwarmNodesResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -188,7 +191,7 @@ impl Resolve<ReadArgs> for InspectSwarmNode {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmNodeResponse> {
) -> mogh_error::Result<InspectSwarmNodeResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -210,7 +213,7 @@ impl Resolve<ReadArgs> for ListSwarmServices {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmServicesResponse> {
) -> mogh_error::Result<ListSwarmServicesResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -231,7 +234,7 @@ impl Resolve<ReadArgs> for InspectSwarmService {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmServiceResponse> {
) -> mogh_error::Result<InspectSwarmServiceResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -253,7 +256,7 @@ impl Resolve<ReadArgs> for GetSwarmServiceLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetSwarmServiceLogResponse> {
) -> mogh_error::Result<GetSwarmServiceLogResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -280,7 +283,7 @@ impl Resolve<ReadArgs> for SearchSwarmServiceLog {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<SearchSwarmServiceLogResponse> {
) -> mogh_error::Result<SearchSwarmServiceLogResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -309,7 +312,7 @@ impl Resolve<ReadArgs> for ListSwarmTasks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmTasksResponse> {
) -> mogh_error::Result<ListSwarmTasksResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -330,7 +333,7 @@ impl Resolve<ReadArgs> for InspectSwarmTask {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmTaskResponse> {
) -> mogh_error::Result<InspectSwarmTaskResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -352,7 +355,7 @@ impl Resolve<ReadArgs> for ListSwarmSecrets {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmSecretsResponse> {
) -> mogh_error::Result<ListSwarmSecretsResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -373,7 +376,7 @@ impl Resolve<ReadArgs> for InspectSwarmSecret {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmSecretResponse> {
) -> mogh_error::Result<InspectSwarmSecretResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -395,7 +398,7 @@ impl Resolve<ReadArgs> for ListSwarmConfigs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmConfigsResponse> {
) -> mogh_error::Result<ListSwarmConfigsResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -416,7 +419,7 @@ impl Resolve<ReadArgs> for InspectSwarmConfig {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmConfigResponse> {
) -> mogh_error::Result<InspectSwarmConfigResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -438,7 +441,7 @@ impl Resolve<ReadArgs> for ListSwarmStacks {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListSwarmStacksResponse> {
) -> mogh_error::Result<ListSwarmStacksResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
@@ -459,7 +462,7 @@ impl Resolve<ReadArgs> for InspectSwarmStack {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<InspectSwarmStackResponse> {
) -> mogh_error::Result<InspectSwarmStackResponse> {
let swarm = get_check_permissions::<Swarm>(
&self.swarm,
user,
+7 -7
View File
@@ -8,7 +8,7 @@ use komodo_client::{
},
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::get_all_tags, permission::get_check_permissions,
@@ -21,7 +21,7 @@ impl Resolve<ReadArgs> for GetResourceSync {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
Ok(
get_check_permissions::<ResourceSync>(
&self.sync,
@@ -37,7 +37,7 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Vec<ResourceSyncListItem>> {
) -> mogh_error::Result<Vec<ResourceSyncListItem>> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -59,7 +59,7 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListFullResourceSyncsResponse> {
) -> mogh_error::Result<ListFullResourceSyncsResponse> {
let all_tags = if self.query.tags.is_empty() {
vec![]
} else {
@@ -81,7 +81,7 @@ impl Resolve<ReadArgs> for GetResourceSyncActionState {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ResourceSyncActionState> {
) -> mogh_error::Result<ResourceSyncActionState> {
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
user,
@@ -102,7 +102,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetResourceSyncsSummaryResponse> {
) -> mogh_error::Result<GetResourceSyncsSummaryResponse> {
let resource_syncs =
resource::list_full_for_user::<ResourceSync>(
Default::default(),
@@ -120,7 +120,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
for resource_sync in resource_syncs {
res.total += 1;
if !(resource_sync.info.pending_deploy.to_deploy == 0
if !(resource_sync.info.pending_deploys.is_empty()
&& resource_sync.info.resource_updates.is_empty()
&& resource_sync.info.variable_updates.is_empty()
&& resource_sync.info.user_group_updates.is_empty())
+6 -3
View File
@@ -7,20 +7,23 @@ use komodo_client::{
api::read::{GetTag, ListTags},
entities::tag::Tag,
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{helpers::query::get_tag, state::db_client};
use super::ReadArgs;
impl Resolve<ReadArgs> for GetTag {
async fn resolve(self, _: &ReadArgs) -> serror::Result<Tag> {
async fn resolve(self, _: &ReadArgs) -> mogh_error::Result<Tag> {
Ok(get_tag(&self.tag).await?)
}
}
impl Resolve<ReadArgs> for ListTags {
async fn resolve(self, _: &ReadArgs) -> serror::Result<Vec<Tag>> {
async fn resolve(
self,
_: &ReadArgs,
) -> mogh_error::Result<Vec<Tag>> {
let res = find_collect(
&db_client().tags,
self.query,
+5 -5
View File
@@ -13,9 +13,9 @@ use komodo_client::{
user::User,
},
};
use mogh_error::AddStatusCode;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCode;
use crate::{
helpers::periphery_client, permission::get_check_permissions,
@@ -30,7 +30,7 @@ impl Resolve<ReadArgs> for ListTerminals {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListTerminalsResponse> {
) -> mogh_error::Result<ListTerminalsResponse> {
let Some(target) = self.target else {
return list_all_terminals_for_user(user, self.use_names).await;
};
@@ -88,7 +88,7 @@ impl Resolve<ReadArgs> for ListTerminals {
async fn list_all_terminals_for_user(
user: &User,
use_names: bool,
) -> serror::Result<Vec<Terminal>> {
) -> mogh_error::Result<Vec<Terminal>> {
let (mut servers, stacks, deployments) = tokio::try_join!(
resource::list_full_for_user::<Server>(
Default::default(),
@@ -230,7 +230,7 @@ async fn list_all_terminals_for_user(
async fn list_terminals_on_server(
server: &Server,
target: Option<TerminalTarget>,
) -> serror::Result<Vec<Terminal>> {
) -> mogh_error::Result<Vec<Terminal>> {
periphery_client(server)
.await?
.request(periphery_client::api::terminal::ListTerminals {
+122 -342
View File
@@ -1,5 +1,8 @@
use anyhow::Context;
use database::mungos::find::find_collect;
use database::{
bson::doc,
mungos::{find::find_collect, mongodb::options::FindOptions},
};
use komodo_client::{
api::read::{
ExportAllResourcesToToml, ExportAllResourcesToTomlResponse,
@@ -15,7 +18,7 @@ use komodo_client::{
user::User,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
helpers::query::{
@@ -38,122 +41,45 @@ 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?
};
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)),
macro_rules! extend_targets {
($($Type:ident),* $(,)?) => {
$(
targets.extend(
resource::list_full_for_user::<$Type>(
ResourceQuery::builder().tags(tags).build(),
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,
);
Ok(targets)
}
@@ -161,7 +87,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
async fn resolve(
self,
args: &ReadArgs,
) -> serror::Result<ExportAllResourcesToTomlResponse> {
) -> mogh_error::Result<ExportAllResourcesToTomlResponse> {
let targets = if self.include_resources {
get_all_targets(&self.tags, &args.user).await?
} else {
@@ -187,6 +113,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
targets,
user_groups,
include_variables: self.include_variables,
existing: self.existing,
}
.resolve(args)
.await
@@ -197,136 +124,69 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
async fn resolve(
self,
args: &ReadArgs,
) -> serror::Result<ExportResourcesToTomlResponse> {
) -> mogh_error::Result<ExportResourcesToTomlResponse> {
let 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);
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::Swarm(id) => {
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,
))
convert_target!(id, Server, servers)
}
ResourceTarget::Swarm(id) => {
convert_target!(id, Swarm, swarms)
}
ResourceTarget::Stack(id) => {
let mut stack = get_check_permissions::<Stack>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Stack::replace_ids(&mut stack);
res.stacks.push(convert_resource::<Stack>(
stack,
false,
vec![],
&id_to_tags,
))
convert_target!(id, Stack, stacks)
}
ResourceTarget::Deployment(id) => {
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,
))
convert_target!(id, Deployment, deployments)
}
ResourceTarget::Build(id) => {
let mut build = get_check_permissions::<Build>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Build::replace_ids(&mut build);
res.builds.push(convert_resource::<Build>(
build,
false,
vec![],
&id_to_tags,
))
}
ResourceTarget::Repo(id) => {
let mut repo = get_check_permissions::<Repo>(
&id,
user,
PermissionLevel::Read.into(),
)
.await?;
Repo::replace_ids(&mut repo);
res.repos.push(convert_resource::<Repo>(
repo,
false,
vec![],
&id_to_tags,
))
convert_target!(id, Build, builds)
}
ResourceTarget::Repo(id) => convert_target!(id, Repo, repos),
ResourceTarget::Procedure(id) => {
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,
));
convert_target!(id, Procedure, procedures)
}
ResourceTarget::Action(id) => {
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,
));
convert_target!(id, Action, actions)
}
ResourceTarget::Builder(id) => {
convert_target!(id, Builder, builders)
}
ResourceTarget::Alerter(id) => {
convert_target!(id, Alerter, alerters)
}
ResourceTarget::ResourceSync(id) => {
let mut sync = get_check_permissions::<ResourceSync>(
@@ -349,36 +209,6 @@ 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,
};
}
@@ -388,18 +218,21 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
.context("failed to add user groups")?;
if include_variables {
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();
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();
}
let toml = serialize_resources_toml(res)
@@ -434,85 +267,32 @@ fn serialize_resources_toml(
) -> anyhow::Result<String> {
let mut toml = String::new();
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)?;
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)?;
}
)*
};
}
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() {
+3 -3
View File
@@ -14,7 +14,7 @@ use komodo_client::{
user::User,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{
config::core_config,
@@ -32,7 +32,7 @@ impl Resolve<ReadArgs> for ListUpdates {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListUpdatesResponse> {
) -> mogh_error::Result<ListUpdatesResponse> {
let query = user_resource_target_query(user, self.query).await?;
let usernames = find_collect(&db_client().users, None, None)
@@ -92,7 +92,7 @@ impl Resolve<ReadArgs> for GetUpdate {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let update = find_one_by_id(&db_client().updates, &self.id)
.await
.context("failed to query to db")?
+18 -7
View File
@@ -13,7 +13,7 @@ use komodo_client::{
},
entities::user::{UserConfig, admin_service_user},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{helpers::query::get_user, state::db_client};
@@ -23,7 +23,7 @@ impl Resolve<ReadArgs> for GetUsername {
async fn resolve(
self,
_: &ReadArgs,
) -> serror::Result<GetUsernameResponse> {
) -> mogh_error::Result<GetUsernameResponse> {
if let Some(user) = admin_service_user(&self.user_id) {
return Ok(GetUsernameResponse {
username: user.username,
@@ -53,7 +53,7 @@ impl Resolve<ReadArgs> for FindUser {
async fn resolve(
self,
ReadArgs { user: admin }: &ReadArgs,
) -> serror::Result<FindUserResponse> {
) -> mogh_error::Result<FindUserResponse> {
if !admin.admin {
return Err(anyhow!("This method is admin only.").into());
}
@@ -65,15 +65,26 @@ impl Resolve<ReadArgs> for ListUsers {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListUsersResponse> {
) -> mogh_error::Result<ListUsersResponse> {
if !user.admin {
return Err(
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,
None,
filter,
FindOptions::builder().sort(doc! { "username": 1 }).build(),
)
.await
@@ -87,7 +98,7 @@ impl Resolve<ReadArgs> for ListApiKeys {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListApiKeysResponse> {
) -> mogh_error::Result<ListApiKeysResponse> {
let api_keys = find_collect(
&db_client().api_keys,
doc! { "user_id": &user.id },
@@ -109,7 +120,7 @@ impl Resolve<ReadArgs> for ListApiKeysForServiceUser {
async fn resolve(
self,
ReadArgs { user: admin }: &ReadArgs,
) -> serror::Result<ListApiKeysForServiceUserResponse> {
) -> mogh_error::Result<ListApiKeysForServiceUserResponse> {
if !admin.admin {
return Err(anyhow!("This method is admin only.").into());
}
+3 -3
View File
@@ -9,7 +9,7 @@ use database::mungos::{
},
};
use komodo_client::api::read::*;
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::state::db_client;
@@ -19,7 +19,7 @@ impl Resolve<ReadArgs> for GetUserGroup {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetUserGroupResponse> {
) -> mogh_error::Result<GetUserGroupResponse> {
let mut filter = match ObjectId::from_str(&self.user_group) {
Ok(id) => doc! { "_id": id },
Err(_) => doc! { "name": &self.user_group },
@@ -43,7 +43,7 @@ impl Resolve<ReadArgs> for ListUserGroups {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListUserGroupsResponse> {
) -> mogh_error::Result<ListUserGroupsResponse> {
let mut filter = Document::new();
if !user.admin {
filter.insert("users", &user.id);
+3 -3
View File
@@ -4,7 +4,7 @@ use database::mungos::{
find::find_collect, mongodb::options::FindOptions,
};
use komodo_client::api::read::*;
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{helpers::query::get_variable, state::db_client};
@@ -14,7 +14,7 @@ impl Resolve<ReadArgs> for GetVariable {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<GetVariableResponse> {
) -> mogh_error::Result<GetVariableResponse> {
let mut variable = get_variable(&self.name).await?;
if !variable.is_secret || user.admin {
return Ok(variable);
@@ -28,7 +28,7 @@ impl Resolve<ReadArgs> for ListVariables {
async fn resolve(
self,
ReadArgs { user }: &ReadArgs,
) -> serror::Result<ListVariablesResponse> {
) -> mogh_error::Result<ListVariablesResponse> {
let variables = find_collect(
&db_client().variables,
None,
+11 -5
View File
@@ -1,16 +1,19 @@
use anyhow::Context;
use axum::{Extension, Router, middleware, routing::post};
use komodo_client::{api::terminal::*, entities::user::User};
use serror::Json;
use mogh_auth_server::middleware::authenticate_request;
use mogh_error::Json;
use crate::{
auth::auth_request, helpers::terminal::setup_target_for_user,
auth::KomodoAuthImpl, helpers::terminal::setup_target_for_user,
};
pub fn router() -> Router {
Router::new()
.route("/execute", post(execute_terminal))
.layer(middleware::from_fn(auth_request))
.layer(middleware::from_fn(
authenticate_request::<KomodoAuthImpl, true>,
))
}
// =================
@@ -35,8 +38,11 @@ async fn execute_terminal(
command,
init,
}): Json<ExecuteTerminalBody>,
) -> serror::Result<axum::body::Body> {
info!("/terminal/execute request | user: {}", user.username);
) -> mogh_error::Result<axum::body::Body> {
info!(
"TERMINAL EXECUTE REQUEST | USER: {} ({})",
user.username, user.id
);
let (target, terminal, periphery) =
setup_target_for_user(target, terminal, init, &user).await?;
-97
View File
@@ -1,97 +0,0 @@
use anyhow::{Context as _, anyhow};
use database::bson::doc;
use komodo_client::{
api::user::{
CreateApiKey, CreateApiKeyResponse, DeleteApiKey,
DeleteApiKeyResponse,
},
entities::{api_key::ApiKey, komodo_timestamp, random_string},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::{AddStatusCode as _, AddStatusCodeError as _};
use crate::{
helpers::{query::get_user, validations::validate_api_key_name},
state::db_client,
};
use super::UserArgs;
const SECRET_LENGTH: usize = 40;
const BCRYPT_COST: u32 = 10;
impl Resolve<UserArgs> for CreateApiKey {
#[instrument(
"CreateApiKey",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<CreateApiKeyResponse> {
let user = get_user(&user.id).await?;
validate_api_key_name(&self.name)
.status_code(StatusCode::BAD_REQUEST)?;
let key = format!("K_{}_K", random_string(SECRET_LENGTH));
let secret = format!("S_{}_S", random_string(SECRET_LENGTH));
let secret_hash = bcrypt::hash(&secret, BCRYPT_COST)
.context("Failed at hashing secret string")?;
let api_key = ApiKey {
name: self.name,
key: key.clone(),
secret: secret_hash,
user_id: user.id.clone(),
created_at: komodo_timestamp(),
expires: self.expires,
};
db_client()
.api_keys
.insert_one(api_key)
.await
.context("Failed to create api key on database")?;
Ok(CreateApiKeyResponse { key, secret })
}
}
impl Resolve<UserArgs> for DeleteApiKey {
#[instrument(
"DeleteApiKey",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<DeleteApiKeyResponse> {
let client = db_client();
let key = client
.api_keys
.find_one(doc! { "key": &self.key })
.await
.context("Failed at database query")?
.context("No api key with key found")?;
if user.id != key.user_id {
return Err(
anyhow!("Api key does not belong to user")
.status_code(StatusCode::FORBIDDEN),
);
}
client
.api_keys
.delete_one(doc! { "key": key.key })
.await
.context("Failed to delete api key from database")?;
Ok(DeleteApiKeyResponse {})
}
}
-367
View File
@@ -1,367 +0,0 @@
use std::{collections::VecDeque, time::Instant};
use anyhow::{Context, anyhow};
use axum::{
Extension, Json, Router, extract::Path, middleware, routing::post,
};
use database::{
bson::{doc, to_bson},
mungos::by_id::update_one_by_id,
};
use derive_variants::EnumVariants;
use komodo_client::{
api::user::*,
entities::{NoData, komodo_timestamp, user::User},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use response::Response;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::{AddStatusCode as _, AddStatusCodeError};
use tower_sessions::Session;
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::auth_request,
config::core_config,
helpers::{
query::get_user,
validations::{validate_password, validate_username},
},
state::db_client,
};
use super::Variant;
mod api_key;
mod passkey;
mod totp;
pub struct UserArgs {
pub user: User,
/// Per-client session state
pub session: Option<Session>,
}
#[typeshare]
#[derive(
Debug, Clone, Serialize, Deserialize, Resolve, EnumVariants,
)]
#[args(UserArgs)]
#[response(Response)]
#[error(serror::Error)]
#[serde(tag = "type", content = "params")]
enum UserRequest {
PushRecentlyViewed(PushRecentlyViewed),
SetLastSeenUpdate(SetLastSeenUpdate),
UpdateUsername(UpdateUsername),
UpdatePassword(UpdatePassword),
CreateApiKey(CreateApiKey),
DeleteApiKey(DeleteApiKey),
BeginTotpEnrollment(BeginTotpEnrollment),
ConfirmTotpEnrollment(ConfirmTotpEnrollment),
UnenrollTotp(UnenrollTotp),
BeginPasskeyEnrollment(BeginPasskeyEnrollment),
ConfirmPasskeyEnrollment(ConfirmPasskeyEnrollment),
UnenrollPasskey(UnenrollPasskey),
BeginThirdPartyLoginLink(BeginThirdPartyLoginLink),
UnlinkLogin(UnlinkLogin),
UpdateThirdPartySkip2fa(UpdateThirdPartySkip2fa),
}
pub fn router() -> Router {
Router::new()
.route("/", post(handler))
.route("/{variant}", post(variant_handler))
.layer(middleware::from_fn(auth_request))
}
async fn variant_handler(
session: Session,
user: Extension<User>,
Path(Variant { variant }): Path<Variant>,
Json(params): Json<serde_json::Value>,
) -> serror::Result<axum::response::Response> {
let req: UserRequest = serde_json::from_value(json!({
"type": variant,
"params": params,
}))?;
handler(session, user, Json(req)).await
}
async fn handler(
session: Session,
Extension(user): Extension<User>,
Json(request): Json<UserRequest>,
) -> serror::Result<axum::response::Response> {
let timer = Instant::now();
let req_id = Uuid::new_v4();
debug!(
"/user request {req_id} | user: {} ({})",
user.username, user.id
);
let res = request
.resolve(&UserArgs {
user,
session: Some(session),
})
.await;
if let Err(e) = &res {
warn!("/user request {req_id} error: {:#}", e.error);
}
let elapsed = timer.elapsed();
debug!("/user request {req_id} | resolve time: {elapsed:?}");
res.map(|res| res.0)
}
const RECENTLY_VIEWED_MAX: usize = 10;
impl Resolve<UserArgs> for PushRecentlyViewed {
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<PushRecentlyViewedResponse> {
let user = get_user(&user.id).await?;
let (resource_type, id) = self.resource.extract_variant_id();
let field = format!("recents.{resource_type}");
let update = match user.recents.get(&resource_type) {
Some(recents) => {
let mut recents = recents
.iter()
.filter(|_id| !id.eq(*_id))
.take(RECENTLY_VIEWED_MAX - 1)
.collect::<VecDeque<_>>();
recents.push_front(id);
doc! { &field: to_bson(&recents)? }
}
None => {
doc! { &field: [id] }
}
};
update_one_by_id(
&db_client().users,
&user.id,
database::mungos::update::Update::Set(update),
None,
)
.await
.with_context(|| format!("Failed to update user '{field}'"))?;
Ok(PushRecentlyViewedResponse {})
}
}
impl Resolve<UserArgs> for SetLastSeenUpdate {
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<SetLastSeenUpdateResponse> {
update_one_by_id(
&db_client().users,
&user.id,
database::mungos::update::Update::Set(doc! {
"last_update_view": komodo_timestamp()
}),
None,
)
.await
.context("Failed to update user 'last_update_view'")?;
Ok(SetLastSeenUpdateResponse {})
}
}
//
impl Resolve<UserArgs> for UpdateUsername {
#[instrument(
"UpdateUsername",
skip_all,
fields(
operator = user.id,
new_username = self.username,
)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UpdateUsernameResponse> {
check_locked(&user.username)?;
validate_username(&self.username)?;
let db = db_client();
if db
.users
.find_one(doc! { "username": &self.username })
.await
.context("Failed to query for existing users")?
.is_some()
{
return Err(anyhow!("Username already taken.").into());
}
let update = doc! { "$set": { "username": self.username } };
update_one_by_id(&db.users, &user.id, update, None)
.await
.context("Failed to update user username on database.")?;
Ok(NoData {})
}
}
//
impl Resolve<UserArgs> for UpdatePassword {
#[instrument(
"UpdatePassword",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UpdatePasswordResponse> {
check_locked(&user.username)?;
validate_password(&self.password)
.status_code(StatusCode::BAD_REQUEST)?;
db_client().set_user_password(user, &self.password).await?;
Ok(NoData {})
}
}
//
#[derive(Serialize, Deserialize)]
pub struct SessionThirdPartyLinkInfo {
pub user_id: String,
}
impl SessionThirdPartyLinkInfo {
pub const KEY: &str = "third-party-link-info";
}
impl Resolve<UserArgs> for BeginThirdPartyLoginLink {
#[instrument(
"BeginThirdPartyLoginLink",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, session }: &UserArgs,
) -> serror::Result<BeginThirdPartyLoginLinkResponse> {
check_locked(&user.username)?;
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
session
.insert(
SessionThirdPartyLinkInfo::KEY,
SessionThirdPartyLinkInfo {
user_id: user.id.clone(),
},
)
.await
.context(
"Failed to insert third party link info into client session",
)?;
Ok(NoData {})
}
}
impl Resolve<UserArgs> for UnlinkLogin {
#[instrument(
"UnlinkLogin",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UnlinkLoginResponse> {
check_locked(&user.username)?;
let field = match self.provider.to_lowercase().as_str() {
"local" => "linked_logins.Local",
"github" => "linked_logins.Github",
"google" => "linked_logins.Google",
"oidc" => "linked_logins.Oidc",
_ => {
return Err(
anyhow!(
"Unrecognized third party login provider: {}",
self.provider
)
.status_code(StatusCode::BAD_REQUEST),
);
}
};
let update = doc! {
"$unset": {
field: ""
}
};
update_one_by_id(&db_client().users, &user.id, update, None)
.await
.context("Failed to unlink third partly login on database")?;
Ok(NoData {})
}
}
impl Resolve<UserArgs> for UpdateThirdPartySkip2fa {
#[instrument(
"UpdateThirdPartySkip2fa",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UpdateThirdPartySkip2faResponse> {
check_locked(&user.username)?;
let update = doc! {
"$set": {
"third_party_skip_2fa": self.skip
}
};
update_one_by_id(&db_client().users, &user.id, update, None)
.await
.context("Failed to unlink third partly login on database")?;
Ok(NoData {})
}
}
fn check_locked(username: &str) -> serror::Result<()> {
for locked_username in &core_config().lock_login_credentials_for {
if locked_username == "__ALL__" || *locked_username == username {
return Err(
anyhow!("User login credentials are locked.")
.status_code(StatusCode::UNAUTHORIZED),
);
}
}
Ok(())
}
-151
View File
@@ -1,151 +0,0 @@
use anyhow::Context as _;
use database::{
bson::{doc, to_bson},
mungos::by_id::update_one_by_id,
};
use komodo_client::{
api::user::{
BeginPasskeyEnrollment, BeginPasskeyEnrollmentResponse,
ConfirmPasskeyEnrollment, ConfirmPasskeyEnrollmentResponse,
UnenrollPasskey, UnenrollPasskeyResponse,
},
entities::komodo_timestamp,
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use webauthn_rs::prelude::PasskeyRegistration;
use crate::state::{db_client, webauthn};
use super::UserArgs;
#[derive(Serialize, Deserialize)]
struct SessionPasskeyEnrollment {
user_id: String,
state: PasskeyRegistration,
}
impl SessionPasskeyEnrollment {
const KEY: &str = "passkey-enrollment";
}
impl Resolve<UserArgs> for BeginPasskeyEnrollment {
#[instrument(
"BeginPasskeyEnrollment",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, session }: &UserArgs,
) -> serror::Result<BeginPasskeyEnrollmentResponse> {
super::check_locked(&user.username)?;
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let webauthn = webauthn().context(
"No webauthn provider available, invalid KOMODO_HOST config",
)?;
// Get two parts from this, the first is returned to the client.
// The second must stay server side and is used in confirmation flow.
let (challenge, state) = webauthn.start_passkey_registration(
Uuid::new_v4(),
&user.username,
&user.username,
None,
)?;
session
.insert(
SessionPasskeyEnrollment::KEY,
SessionPasskeyEnrollment {
user_id: user.id.clone(),
state
},
)
.await
.context(
"Failed to store passkey enrollment state in server side client session",
)?;
Ok(challenge)
}
}
//
impl Resolve<UserArgs> for ConfirmPasskeyEnrollment {
#[instrument(
"ConfirmPasskeyEnrollment",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, session }: &UserArgs,
) -> serror::Result<ConfirmPasskeyEnrollmentResponse> {
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let webauthn = webauthn().context(
"No webauthn provider available, invalid KOMODO_HOST config",
)?;
let SessionPasskeyEnrollment { user_id, state } = session
.remove(SessionPasskeyEnrollment::KEY)
.await
.context("Passkey enrollment was not initiated correctly")?
.context(
"Passkey enrollment was not initiated correctly or timed out",
)?;
let passkey = webauthn
.finish_passkey_registration(&self.credential, &state)
.context("Failed to finish passkey registration")?;
let passkey = to_bson(&passkey)
.context("Failed to serialize passkey to BSON")?;
let update = doc! {
"$set": {
"passkey.passkey": passkey,
"passkey.created_at": komodo_timestamp()
}
};
update_one_by_id(&db_client().users, &user_id, update, None)
.await
.context("Failed to update user passkey options on database")?;
Ok(ConfirmPasskeyEnrollmentResponse {})
}
}
//
impl Resolve<UserArgs> for UnenrollPasskey {
#[instrument(
"UnenrollPasskey",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UnenrollPasskeyResponse> {
let update = doc! {
"$set": {
"passkey.passkey": null,
"passkey.created_at": 0
}
};
update_one_by_id(&db_client().users, &user.id, update, None)
.await
.context("Failed to update user passkey options on database")?;
Ok(UnenrollPasskeyResponse {})
}
}
-158
View File
@@ -1,158 +0,0 @@
use anyhow::{Context as _, anyhow};
use data_encoding::BASE32_NOPAD;
use database::{
bson::doc, hash_password, mungos::by_id::update_one_by_id,
};
use komodo_client::{
api::user::{
BeginTotpEnrollment, BeginTotpEnrollmentResponse,
ConfirmTotpEnrollment, ConfirmTotpEnrollmentResponse,
UnenrollTotp, UnenrollTotpResponse,
},
entities::{komodo_timestamp, random_bytes, random_string},
};
use reqwest::StatusCode;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use serror::AddStatusCodeError as _;
use crate::{auth::totp::make_totp, state::db_client};
use super::UserArgs;
const TOTP_ENROLLMENT_SECRET_LENGTH: usize = 20;
#[derive(Serialize, Deserialize)]
struct SessionTotpEnrollment {
secret: Vec<u8>,
}
impl SessionTotpEnrollment {
const KEY: &str = "totp-enrollment";
}
impl Resolve<UserArgs> for BeginTotpEnrollment {
#[instrument(
"BeginTotpEnrollment",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, session }: &UserArgs,
) -> serror::Result<BeginTotpEnrollmentResponse> {
super::check_locked(&user.username)?;
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let secret = random_bytes(TOTP_ENROLLMENT_SECRET_LENGTH);
let totp = make_totp(secret.clone(), user.id.clone())?;
let png = totp
.get_qr_base64()
.map_err(anyhow::Error::msg)
.context("Failed to generate QR code png")?;
session
.insert(
SessionTotpEnrollment::KEY,
SessionTotpEnrollment { secret },
)
.await?;
Ok(BeginTotpEnrollmentResponse {
uri: totp.get_url(),
png,
})
}
}
impl Resolve<UserArgs> for ConfirmTotpEnrollment {
#[instrument(
"ConfirmTotpEnrollment",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, session }: &UserArgs,
) -> serror::Result<ConfirmTotpEnrollmentResponse> {
let session = session.as_ref().context(
"Method called in invalid context. This should not happen",
)?;
let SessionTotpEnrollment { secret } = session
.remove(SessionTotpEnrollment::KEY)
.await
.context("Totp enrollment was not initiated correctly")?
.context(
"Totp enrollment was not initiated correctly or timed out",
)?;
let encoded_secret = BASE32_NOPAD.encode(&secret);
let totp = make_totp(secret, None)?;
let valid = totp
.check_current(&self.code)
.context("Failed to check code validity")?;
if !valid {
return Err(anyhow!(
"The provided code was not valid. Please try BeginTotpEnrollment flow again."
).status_code(StatusCode::BAD_REQUEST));
}
let recovery_codes =
(0..10).map(|_| random_string(20)).collect::<Vec<_>>();
let hashed_recovery_codes = recovery_codes
.iter()
.map(hash_password)
.collect::<anyhow::Result<Vec<_>>>()
.context("Failed to generate valid recovery codes")?;
update_one_by_id(
&db_client().users,
&user.id,
doc! {
"$set": {
"totp.secret": encoded_secret,
"totp.confirmed_at": komodo_timestamp(),
"totp.recovery_codes": hashed_recovery_codes,
}
},
None,
)
.await
.context("Failed to update user totp fields on database")?;
Ok(ConfirmTotpEnrollmentResponse { recovery_codes })
}
}
impl Resolve<UserArgs> for UnenrollTotp {
#[instrument(
"UnenrollTotp",
skip_all,
fields(operator = user.id)
)]
async fn resolve(
self,
UserArgs { user, .. }: &UserArgs,
) -> serror::Result<UnenrollTotpResponse> {
update_one_by_id(
&db_client().users,
&user.id,
doc! {
"$set": {
"totp.secret": "",
"totp.confirmed_at": 0,
"totp.recovery_codes": [],
}
},
None,
)
.await
.context("Failed to clear user totp fields on database")?;
Ok(UnenrollTotpResponse {})
}
}
+6 -6
View File
@@ -4,7 +4,7 @@ use komodo_client::{
action::Action, permission::PermissionLevel, update::Update,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{permission::get_check_permissions, resource};
@@ -23,7 +23,7 @@ impl Resolve<WriteArgs> for CreateAction {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Action> {
) -> mogh_error::Result<Action> {
resource::create::<Action>(&self.name, self.config, None, user)
.await
}
@@ -42,7 +42,7 @@ impl Resolve<WriteArgs> for CopyAction {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Action> {
) -> mogh_error::Result<Action> {
let Action { config, .. } = get_check_permissions::<Action>(
&self.id,
user,
@@ -67,7 +67,7 @@ impl Resolve<WriteArgs> for UpdateAction {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Action> {
) -> mogh_error::Result<Action> {
Ok(resource::update::<Action>(&self.id, self.config, user).await?)
}
}
@@ -85,7 +85,7 @@ impl Resolve<WriteArgs> for RenameAction {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Action>(&self.id, &self.name, user).await?)
}
}
@@ -102,7 +102,7 @@ impl Resolve<WriteArgs> for DeleteAction {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Action> {
) -> mogh_error::Result<Action> {
Ok(resource::delete::<Action>(&self.id, user).await?)
}
}
+2 -2
View File
@@ -3,9 +3,9 @@ use std::str::FromStr;
use anyhow::{Context, anyhow};
use database::mungos::mongodb::bson::{doc, oid::ObjectId};
use komodo_client::{api::write::CloseAlert, entities::NoData};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{api::write::WriteArgs, state::db_client};
+6 -6
View File
@@ -4,7 +4,7 @@ use komodo_client::{
alerter::Alerter, permission::PermissionLevel, update::Update,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{permission::get_check_permissions, resource};
@@ -23,7 +23,7 @@ impl Resolve<WriteArgs> for CreateAlerter {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Alerter> {
) -> mogh_error::Result<Alerter> {
resource::create::<Alerter>(&self.name, self.config, None, user)
.await
}
@@ -42,7 +42,7 @@ impl Resolve<WriteArgs> for CopyAlerter {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Alerter> {
) -> mogh_error::Result<Alerter> {
let Alerter { config, .. } = get_check_permissions::<Alerter>(
&self.id,
user,
@@ -66,7 +66,7 @@ impl Resolve<WriteArgs> for DeleteAlerter {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Alerter> {
) -> mogh_error::Result<Alerter> {
Ok(resource::delete::<Alerter>(&self.id, user).await?)
}
}
@@ -84,7 +84,7 @@ impl Resolve<WriteArgs> for UpdateAlerter {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Alerter> {
) -> mogh_error::Result<Alerter> {
Ok(
resource::update::<Alerter>(&self.id, self.config, user)
.await?,
@@ -105,7 +105,7 @@ impl Resolve<WriteArgs> for RenameAlerter {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Alerter>(&self.id, &self.name, user).await?)
}
}
+13 -10
View File
@@ -19,10 +19,10 @@ use komodo_client::{
update::Update,
},
};
use mogh_resolver::Resolve;
use periphery_client::api::build::{
GetDockerfileContentsOnHost, WriteDockerfileContentsToHost,
};
use resolver_api::Resolve;
use tokio::fs;
use crate::{
@@ -54,7 +54,7 @@ impl Resolve<WriteArgs> for CreateBuild {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Build> {
) -> mogh_error::Result<Build> {
resource::create::<Build>(&self.name, self.config, None, user)
.await
}
@@ -73,7 +73,7 @@ impl Resolve<WriteArgs> for CopyBuild {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Build> {
) -> mogh_error::Result<Build> {
let Build { mut config, .. } = get_check_permissions::<Build>(
&self.id,
user,
@@ -99,7 +99,7 @@ impl Resolve<WriteArgs> for DeleteBuild {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Build> {
) -> mogh_error::Result<Build> {
Ok(resource::delete::<Build>(&self.id, user).await?)
}
}
@@ -117,7 +117,7 @@ impl Resolve<WriteArgs> for UpdateBuild {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Build> {
) -> mogh_error::Result<Build> {
Ok(resource::update::<Build>(&self.id, self.config, user).await?)
}
}
@@ -135,7 +135,7 @@ impl Resolve<WriteArgs> for RenameBuild {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Build>(&self.id, &self.name, user).await?)
}
}
@@ -149,7 +149,10 @@ impl Resolve<WriteArgs> for WriteBuildFileContents {
build = self.build,
)
)]
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
async fn resolve(
self,
args: &WriteArgs,
) -> mogh_error::Result<Update> {
let build = get_check_permissions::<Build>(
&self.build,
&args.user,
@@ -226,7 +229,7 @@ async fn write_dockerfile_contents_git(
args: &WriteArgs,
build: Build,
mut update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let WriteBuildFileContents { build: _, contents } = req;
let mut repo_args: RepoExecutionArgs = if !build
@@ -318,7 +321,7 @@ async fn write_dockerfile_contents_git(
return Ok(update);
}
if let Err(e) = secret_file::write_async(&full_path, &contents)
if let Err(e) = mogh_secret_file::write_async(&full_path, &contents)
.await
.with_context(|| {
format!("Failed to write dockerfile contents to {full_path:?}")
@@ -370,7 +373,7 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
) -> mogh_error::Result<NoData> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// build should be able to do this.
let build = get_check_permissions::<Build>(
+6 -6
View File
@@ -4,7 +4,7 @@ use komodo_client::{
builder::Builder, permission::PermissionLevel, update::Update,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{permission::get_check_permissions, resource};
@@ -23,7 +23,7 @@ impl Resolve<WriteArgs> for CreateBuilder {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Builder> {
) -> mogh_error::Result<Builder> {
resource::create::<Builder>(&self.name, self.config, None, user)
.await
}
@@ -42,7 +42,7 @@ impl Resolve<WriteArgs> for CopyBuilder {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Builder> {
) -> mogh_error::Result<Builder> {
let Builder { config, .. } = get_check_permissions::<Builder>(
&self.id,
user,
@@ -66,7 +66,7 @@ impl Resolve<WriteArgs> for DeleteBuilder {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Builder> {
) -> mogh_error::Result<Builder> {
Ok(resource::delete::<Builder>(&self.id, user).await?)
}
}
@@ -84,7 +84,7 @@ impl Resolve<WriteArgs> for UpdateBuilder {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Builder> {
) -> mogh_error::Result<Builder> {
Ok(
resource::update::<Builder>(&self.id, self.config, user)
.await?,
@@ -105,7 +105,7 @@ impl Resolve<WriteArgs> for RenameBuilder {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Builder>(&self.id, &self.name, user).await?)
}
}
+377 -19
View File
@@ -1,33 +1,48 @@
use std::sync::OnceLock;
use anyhow::{Context, anyhow};
use database::mungos::{by_id::update_one_by_id, mongodb::bson::doc};
use futures_util::{StreamExt as _, stream::FuturesOrdered};
use komodo_client::{
api::write::*,
api::{execute::Deploy, write::*},
entities::{
Operation,
Operation, ResourceTarget, SwarmOrServer,
alert::{Alert, AlertData, SeverityLevel},
deployment::{
Deployment, DeploymentImage, DeploymentState,
PartialDeploymentConfig, RestartMode,
Deployment, DeploymentImage, DeploymentInfo, DeploymentState,
PartialDeploymentConfig, RestartMode, extract_registry_domain,
},
docker::container::RestartPolicyNameEnum,
komodo_timestamp,
komodo_timestamp, optional_string,
permission::PermissionLevel,
server::{Server, ServerState},
to_container_compatible_name,
update::Update,
user::{auto_redeploy_user, system_user},
},
};
use mogh_cache::SetCache;
use mogh_resolver::Resolve;
use periphery_client::api::{self, container::InspectContainer};
use resolver_api::Resolve;
use crate::{
alert::send_alerts,
api::execute::{self, ExecuteRequest, ExecutionResult},
helpers::{
periphery_client,
query::get_deployment_state,
update::{add_update, make_update},
query::{get_deployment_state, get_swarm_or_server},
registry_token,
update::{add_update, make_update, poll_update_until_complete},
},
permission::get_check_permissions,
resource,
state::{action_states, db_client, server_status_cache},
resource::{
self, list_full_for_user_using_pattern,
setup_deployment_execution,
},
state::{
action_states, db_client, deployment_status_cache,
image_digest_cache, server_status_cache,
},
};
use super::WriteArgs;
@@ -45,7 +60,7 @@ impl Resolve<WriteArgs> for CreateDeployment {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
) -> mogh_error::Result<Deployment> {
resource::create::<Deployment>(
&self.name,
self.config,
@@ -69,7 +84,7 @@ impl Resolve<WriteArgs> for CopyDeployment {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
) -> mogh_error::Result<Deployment> {
let Deployment { config, .. } =
get_check_permissions::<Deployment>(
&self.id,
@@ -100,7 +115,7 @@ impl Resolve<WriteArgs> for CreateDeploymentFromContainer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
) -> mogh_error::Result<Deployment> {
let server = get_check_permissions::<Server>(
&self.server,
user,
@@ -201,7 +216,7 @@ impl Resolve<WriteArgs> for DeleteDeployment {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
) -> mogh_error::Result<Deployment> {
Ok(resource::delete::<Deployment>(&self.id, user).await?)
}
}
@@ -219,11 +234,35 @@ impl Resolve<WriteArgs> for UpdateDeployment {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Deployment> {
Ok(
) -> mogh_error::Result<Deployment> {
// If the update changes image,
// also update the stored latest image digest.
let image_update = self
.config
.image
.as_ref()
.map(|image| image.as_image().is_some())
.unwrap_or_default();
let deployment =
resource::update::<Deployment>(&self.id, self.config, user)
.await?,
)
.await?;
if image_update {
tokio::spawn(async move {
let _ = (CheckDeploymentForUpdate {
deployment: self.id,
skip_auto_update: false,
wait_for_auto_update: false,
})
.resolve(&WriteArgs {
user: system_user().to_owned(),
})
.await;
});
}
Ok(deployment)
}
}
@@ -240,7 +279,7 @@ impl Resolve<WriteArgs> for RenameDeployment {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let deployment = get_check_permissions::<Deployment>(
&self.id,
user,
@@ -314,3 +353,322 @@ impl Resolve<WriteArgs> for RenameDeployment {
Ok(update)
}
}
//
impl Resolve<WriteArgs> for CheckDeploymentForUpdate {
#[instrument(
"CheckDeploymentForUpdate",
skip_all,
fields(
operator = user.id,
deployment = self.deployment,
skip_auto_update = self.skip_auto_update,
wait_for_auto_update = self.wait_for_auto_update,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<Self::Response> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// deployment should be able to do this.
let (deployment, swarm_or_server) = setup_deployment_execution(
&self.deployment,
user,
PermissionLevel::Execute.into(),
)
.await?;
swarm_or_server.verify_has_target()?;
check_deployment_for_update_inner(
deployment,
&swarm_or_server,
self.skip_auto_update,
self.wait_for_auto_update,
)
.await
.map_err(Into::into)
}
}
/// If it goes down the "update available" path,
/// only send alert if deployment id is not in this cache.
/// If alert is sent, add ID to cache.
/// If later it goes down non "update available" path,
/// remove the id from cache, so next time it does another alert
/// will be sent.
fn deployment_alert_sent_cache() -> &'static SetCache<String> {
static CACHE: OnceLock<SetCache<String>> = OnceLock::new();
CACHE.get_or_init(Default::default)
}
/// Checks remote registry for latest image digest,
/// and saves it to database associated with the deployment.
///
/// Returns true if update is available and auto deploy is false.
/// If auto deploy is true, this will deploy.
#[instrument(
"CheckDeploymentForUpdateInner",
skip_all,
fields(
deployment = deployment.id,
skip_auto_update,
wait_for_auto_update,
)
)]
pub async fn check_deployment_for_update_inner(
deployment: Deployment,
swarm_or_server: &SwarmOrServer,
skip_auto_update: bool,
// Otherwise spawns task to run in background
wait_for_auto_update: bool,
) -> anyhow::Result<CheckDeploymentForUpdateResponse> {
let alert_cache = deployment_alert_sent_cache();
let (image, account, token) = match &deployment.config.image {
DeploymentImage::Image { image } => {
if image.contains('@') {
// Images with a hardcoded digest can't have update.
return Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: false,
});
}
let domain = extract_registry_domain(image)?;
let account =
optional_string(&deployment.config.image_registry_account);
let token = if let Some(account) = &account {
registry_token(&domain, account).await?
} else {
None
};
(image, account, token)
}
DeploymentImage::Build { .. } => {
alert_cache.remove(&deployment.id).await;
// This method not used for build based deployments
// as deployed version vs built version can be inferred from Updates.
return Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: false,
});
}
};
let latest_digest = image_digest_cache()
.get(swarm_or_server, image, account, token)
.await?;
resource::update_info::<Deployment>(
&deployment.id,
&DeploymentInfo {
latest_image_digest: latest_digest.clone(),
},
)
.await?;
let Some((state, Some(current_digests))) =
deployment_status_cache()
.get(&deployment.id)
.await
.map(|s| (s.curr.state, s.curr.image_digests.clone()))
else {
alert_cache.remove(&deployment.id).await;
return Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: false,
});
};
// If not running or latest digest matches current, early return
if !matches!(state, DeploymentState::Running)
|| !latest_digest.update_available(&current_digests)
{
alert_cache.remove(&deployment.id).await;
return Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: false,
});
}
if !skip_auto_update && deployment.config.auto_update {
// Trigger deploy + alert
// Conservatively remove from alert cache so 'skip_auto_update'
// doesn't cause alerts not to be sent on subsequent calls.
alert_cache.remove(&deployment.id).await;
let swarm_id = swarm_or_server.swarm_id().map(str::to_string);
let swarm_name = swarm_or_server.swarm_name().map(str::to_string);
let server_id = swarm_or_server.server_id().map(str::to_string);
let server_name =
swarm_or_server.server_name().map(str::to_string);
let id = deployment.id.clone();
let name = deployment.name.clone();
let image = image.clone();
let run = async move {
match execute::inner_handler(
ExecuteRequest::Deploy(Deploy {
deployment: name.clone(),
stop_signal: None,
stop_time: None,
}),
auto_redeploy_user().to_owned(),
)
.await
{
Ok(res) => {
let ExecutionResult::Single(update) = res else {
unreachable!()
};
let Ok(update) =
poll_update_until_complete(&update.id).await
else {
return;
};
if update.success {
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(id.clone()),
data: AlertData::DeploymentAutoUpdated {
id,
name,
swarm_id,
swarm_name,
server_id,
server_name,
image,
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentAutoUpdated to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
}
Err(e) => {
warn!("Failed to auto update Deployment {name} | {e:#}",)
}
}
};
if wait_for_auto_update {
run.await
} else {
tokio::spawn(run);
}
} else {
// Avoid spamming alerts
if alert_cache.contains(&deployment.id).await {
return Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: true,
});
}
alert_cache.insert(deployment.id.clone()).await;
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Deployment(deployment.id.clone()),
data: AlertData::DeploymentImageUpdateAvailable {
id: deployment.id.clone(),
name: deployment.name.clone(),
swarm_id: swarm_or_server.swarm_id().map(str::to_string),
swarm_name: swarm_or_server.swarm_name().map(str::to_string),
server_id: swarm_or_server.server_id().map(str::to_string),
server_name: swarm_or_server
.server_name()
.map(str::to_string),
image: image.clone(),
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record DeploymentImageUpdateAvailable to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
Ok(CheckDeploymentForUpdateResponse {
deployment: deployment.id,
update_available: !deployment.config.auto_update,
})
}
//
impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
#[instrument(
"BatchCheckDeploymentForUpdate",
skip_all,
fields(
operator = user.id,
pattern = self.pattern,
skip_auto_update = self.skip_auto_update,
wait_for_auto_update = self.wait_for_auto_update,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> Result<Self::Response, Self::Error> {
let deployments = list_full_for_user_using_pattern::<Deployment>(
&self.pattern,
Default::default(),
user,
PermissionLevel::Execute.into(),
&[],
)
.await?;
let res = deployments
.into_iter()
.map(|deployment| async move {
let swarm_or_server = get_swarm_or_server(
&deployment.config.swarm_id,
&deployment.config.server_id,
)
.await?;
swarm_or_server.verify_has_target().map_err(|e| e.error)?;
check_deployment_for_update_inner(
deployment,
&swarm_or_server,
self.skip_auto_update,
self.wait_for_auto_update,
)
.await
})
.collect::<FuturesOrdered<_>>()
.collect::<Vec<_>>()
.await
.into_iter()
.filter_map(|res| {
res
.inspect_err(|e| {
warn!(
"Failed to check deployment for update in batch run | {e:#}"
)
})
.ok()
})
.collect();
Ok(res)
}
}
+51 -19
View File
@@ -2,18 +2,19 @@ use anyhow::Context;
use axum::{
Extension, Router, extract::Path, middleware, routing::post,
};
use derive_variants::{EnumVariants, ExtractVariant};
use komodo_client::{api::write::*, entities::user::User};
use resolver_api::Resolve;
use response::Response;
use mogh_auth_server::middleware::authenticate_request;
use mogh_error::Json;
use mogh_error::Response;
use mogh_resolver::Resolve;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serror::Json;
use strum::Display;
use strum::EnumDiscriminants;
use typeshare::typeshare;
use uuid::Uuid;
use crate::auth::auth_request;
use crate::auth::KomodoAuthImpl;
use super::Variant;
@@ -40,18 +41,23 @@ mod user;
mod user_group;
mod variable;
pub use {
deployment::check_deployment_for_update_inner,
stack::check_stack_for_update_inner,
};
pub struct WriteArgs {
pub user: User,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants,
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
)]
#[variant_derive(Debug, Display)]
#[strum_discriminants(name(WriteRequestMethod), derive(Display))]
#[args(WriteArgs)]
#[response(Response)]
#[error(serror::Error)]
#[error(mogh_error::Error)]
#[serde(tag = "type", content = "params")]
pub enum WriteRequest {
// ==== RESOURCE ====
@@ -88,6 +94,8 @@ pub enum WriteRequest {
RenameStack(RenameStack),
WriteStackFileContents(WriteStackFileContents),
RefreshStackCache(RefreshStackCache),
CheckStackForUpdate(CheckStackForUpdate),
BatchCheckStackForUpdate(BatchCheckStackForUpdate),
// ==== DEPLOYMENT ====
CreateDeployment(CreateDeployment),
@@ -96,6 +104,8 @@ pub enum WriteRequest {
DeleteDeployment(DeleteDeployment),
UpdateDeployment(UpdateDeployment),
RenameDeployment(RenameDeployment),
CheckDeploymentForUpdate(CheckDeploymentForUpdate),
BatchCheckDeploymentForUpdate(BatchCheckDeploymentForUpdate),
// ==== BUILD ====
CreateBuild(CreateBuild),
@@ -158,6 +168,8 @@ pub enum WriteRequest {
DeleteOnboardingKey(DeleteOnboardingKey),
// ==== USER ====
PushRecentlyViewed(PushRecentlyViewed),
SetLastSeenUpdate(SetLastSeenUpdate),
CreateLocalUser(CreateLocalUser),
DeleteUser(DeleteUser),
@@ -211,14 +223,16 @@ pub fn router() -> Router {
Router::new()
.route("/", post(handler))
.route("/{variant}", post(variant_handler))
.layer(middleware::from_fn(auth_request))
.layer(middleware::from_fn(
authenticate_request::<KomodoAuthImpl, true>,
))
}
async fn variant_handler(
user: Extension<User>,
Path(Variant { variant }): Path<Variant>,
Json(params): Json<serde_json::Value>,
) -> serror::Result<axum::response::Response> {
) -> mogh_error::Result<axum::response::Response> {
let req: WriteRequest = serde_json::from_value(json!({
"type": variant,
"params": params,
@@ -229,10 +243,8 @@ async fn variant_handler(
async fn handler(
Extension(user): Extension<User>,
Json(request): Json<WriteRequest>,
) -> serror::Result<axum::response::Response> {
let req_id = Uuid::new_v4();
let res = tokio::spawn(task(req_id, request, user))
) -> mogh_error::Result<axum::response::Response> {
let res = tokio::spawn(task(request, user))
.await
.context("failure in spawned task");
@@ -240,18 +252,38 @@ async fn handler(
}
async fn task(
req_id: Uuid,
request: WriteRequest,
user: User,
) -> serror::Result<axum::response::Response> {
let variant = request.extract_variant();
info!("/write request | {variant} | user: {}", user.username);
) -> mogh_error::Result<axum::response::Response> {
let task_id = Uuid::new_v4();
let method: WriteRequestMethod = (&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",
);
}
let res = request.resolve(&WriteArgs { user }).await;
if let Err(e) = &res {
warn!(
"/write request {req_id} | {variant} | error: {:#}",
task_id = task_id.to_string(),
method = method.to_string(),
user_id,
username,
"WRITE REQUEST | ERROR: {:#}",
e.error
);
}
+52 -11
View File
@@ -10,12 +10,15 @@ use komodo_client::{
komodo_timestamp, onboarding_key::OnboardingKey, random_string,
},
};
use noise::key::EncodedKeyPair;
use mogh_error::{AddStatusCode, AddStatusCodeError};
use mogh_pki::EncodedKeyPair;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::{AddStatusCode, AddStatusCodeError};
use crate::{api::write::WriteArgs, state::db_client};
use crate::{
api::write::WriteArgs, helpers::query::get_all_tags,
state::db_client,
};
//
@@ -35,7 +38,7 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<CreateOnboardingKeyResponse> {
) -> mogh_error::Result<CreateOnboardingKeyResponse> {
if !admin.admin {
return Err(
anyhow!("This call is admin only")
@@ -47,9 +50,27 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
} else {
format!("O_{}_O", random_string(28))
};
let public_key = EncodedKeyPair::from_private_key(&private_key)?
.public
.into_inner();
let public_key = EncodedKeyPair::from_private_key(
mogh_pki::PkiKind::Mutual,
&private_key,
)?
.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,
@@ -57,7 +78,8 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
onboarded: Default::default(),
created_at: komodo_timestamp(),
expires: self.expires,
tags: self.tags,
tags,
privileged: self.privileged,
copy_server: self.copy_server,
create_builder: self.create_builder,
};
@@ -99,7 +121,7 @@ impl Resolve<WriteArgs> for UpdateOnboardingKey {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdateOnboardingKeyResponse> {
) -> mogh_error::Result<UpdateOnboardingKeyResponse> {
if !admin.admin {
return Err(
anyhow!("This call is admin only")
@@ -135,9 +157,28 @@ 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(copy_server) = self.copy_server {
update.insert("copy_server", copy_server);
}
@@ -176,7 +217,7 @@ impl Resolve<WriteArgs> for DeleteOnboardingKey {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<DeleteOnboardingKeyResponse> {
) -> mogh_error::Result<DeleteOnboardingKeyResponse> {
if !admin.admin {
return Err(
anyhow!("This call is admin only")
+7 -8
View File
@@ -8,7 +8,6 @@ use database::mungos::{
options::UpdateOptions,
},
};
use derive_variants::ExtractVariant as _;
use komodo_client::{
api::write::*,
entities::{
@@ -16,7 +15,7 @@ use komodo_client::{
permission::{UserTarget, UserTargetVariant},
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{helpers::query::get_user, state::db_client};
@@ -35,7 +34,7 @@ impl Resolve<WriteArgs> for UpdateUserAdmin {
async fn resolve(
self,
WriteArgs { user: super_admin }: &WriteArgs,
) -> serror::Result<UpdateUserAdminResponse> {
) -> mogh_error::Result<UpdateUserAdminResponse> {
if !super_admin.super_admin {
return Err(
anyhow!("Only super admins can call this method.").into(),
@@ -83,7 +82,7 @@ impl Resolve<WriteArgs> for UpdateUserBasePermissions {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdateUserBasePermissionsResponse> {
) -> mogh_error::Result<UpdateUserBasePermissionsResponse> {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
@@ -149,7 +148,7 @@ impl Resolve<WriteArgs> for UpdatePermissionOnResourceType {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdatePermissionOnResourceTypeResponse> {
) -> mogh_error::Result<UpdatePermissionOnResourceTypeResponse> {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
@@ -227,7 +226,7 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> serror::Result<UpdatePermissionOnTargetResponse> {
) -> mogh_error::Result<UpdatePermissionOnTargetResponse> {
if !admin.admin {
return Err(anyhow!("this method is admin only").into());
}
@@ -296,7 +295,7 @@ impl Resolve<WriteArgs> for UpdatePermissionOnTarget {
/// checks if inner id is actually a `name`, and replaces it with id if so.
async fn extract_user_target_with_validation(
user_target: &UserTarget,
) -> serror::Result<(UserTargetVariant, String)> {
) -> mogh_error::Result<(UserTargetVariant, String)> {
match user_target {
UserTarget::User(ident) => {
let filter = match ObjectId::from_str(ident) {
@@ -332,7 +331,7 @@ async fn extract_user_target_with_validation(
/// checks if inner id is actually a `name`, and replaces it with id if so.
async fn extract_resource_target_with_validation(
resource_target: &ResourceTarget,
) -> serror::Result<(ResourceTargetVariant, String)> {
) -> mogh_error::Result<(ResourceTargetVariant, String)> {
match resource_target {
ResourceTarget::System(_) => {
let res = resource_target.extract_variant_id();
+6 -6
View File
@@ -4,7 +4,7 @@ use komodo_client::{
permission::PermissionLevel, procedure::Procedure, update::Update,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{permission::get_check_permissions, resource};
@@ -23,7 +23,7 @@ impl Resolve<WriteArgs> for CreateProcedure {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CreateProcedureResponse> {
) -> mogh_error::Result<CreateProcedureResponse> {
resource::create::<Procedure>(&self.name, self.config, None, user)
.await
}
@@ -42,7 +42,7 @@ impl Resolve<WriteArgs> for CopyProcedure {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CopyProcedureResponse> {
) -> mogh_error::Result<CopyProcedureResponse> {
let Procedure { config, .. } =
get_check_permissions::<Procedure>(
&self.id,
@@ -73,7 +73,7 @@ impl Resolve<WriteArgs> for UpdateProcedure {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<UpdateProcedureResponse> {
) -> mogh_error::Result<UpdateProcedureResponse> {
Ok(
resource::update::<Procedure>(&self.id, self.config, user)
.await?,
@@ -94,7 +94,7 @@ impl Resolve<WriteArgs> for RenameProcedure {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(
resource::rename::<Procedure>(&self.id, &self.name, user)
.await?,
@@ -114,7 +114,7 @@ impl Resolve<WriteArgs> for DeleteProcedure {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<DeleteProcedureResponse> {
) -> mogh_error::Result<DeleteProcedureResponse> {
Ok(resource::delete::<Procedure>(&self.id, user).await?)
}
}
+8 -8
View File
@@ -10,9 +10,9 @@ use komodo_client::{
provider::{DockerRegistryAccount, GitProviderAccount},
},
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{
helpers::update::{add_update, make_update},
@@ -35,7 +35,7 @@ impl Resolve<WriteArgs> for CreateGitProviderAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CreateGitProviderAccountResponse> {
) -> mogh_error::Result<CreateGitProviderAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can create git provider accounts")
@@ -111,7 +111,7 @@ impl Resolve<WriteArgs> for UpdateGitProviderAccount {
async fn resolve(
mut self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<UpdateGitProviderAccountResponse> {
) -> mogh_error::Result<UpdateGitProviderAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can update git provider accounts")
@@ -199,7 +199,7 @@ impl Resolve<WriteArgs> for DeleteGitProviderAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<DeleteGitProviderAccountResponse> {
) -> mogh_error::Result<DeleteGitProviderAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can delete git provider accounts")
@@ -261,7 +261,7 @@ impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CreateDockerRegistryAccountResponse> {
) -> mogh_error::Result<CreateDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!(
@@ -340,7 +340,7 @@ impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
async fn resolve(
mut self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<UpdateDockerRegistryAccountResponse> {
) -> mogh_error::Result<UpdateDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can update docker registry accounts")
@@ -435,7 +435,7 @@ impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<DeleteDockerRegistryAccountResponse> {
) -> mogh_error::Result<DeleteDockerRegistryAccountResponse> {
if !user.admin {
return Err(
anyhow!("Only admins can delete docker registry accounts")
+7 -7
View File
@@ -15,8 +15,8 @@ use komodo_client::{
update::{Log, Update},
},
};
use mogh_resolver::Resolve;
use periphery_client::api;
use resolver_api::Resolve;
use crate::{
config::core_config,
@@ -44,7 +44,7 @@ impl Resolve<WriteArgs> for CreateRepo {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Repo> {
) -> mogh_error::Result<Repo> {
resource::create::<Repo>(&self.name, self.config, None, user)
.await
}
@@ -63,7 +63,7 @@ impl Resolve<WriteArgs> for CopyRepo {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Repo> {
) -> mogh_error::Result<Repo> {
let Repo { config, .. } = get_check_permissions::<Repo>(
&self.id,
user,
@@ -87,7 +87,7 @@ impl Resolve<WriteArgs> for DeleteRepo {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Repo> {
) -> mogh_error::Result<Repo> {
Ok(resource::delete::<Repo>(&self.id, user).await?)
}
}
@@ -105,7 +105,7 @@ impl Resolve<WriteArgs> for UpdateRepo {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Repo> {
) -> mogh_error::Result<Repo> {
Ok(resource::update::<Repo>(&self.id, self.config, user).await?)
}
}
@@ -123,7 +123,7 @@ impl Resolve<WriteArgs> for RenameRepo {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let repo = get_check_permissions::<Repo>(
&self.id,
user,
@@ -199,7 +199,7 @@ impl Resolve<WriteArgs> for RefreshRepoCache {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
) -> mogh_error::Result<NoData> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// repo should be able to do this.
let repo = get_check_permissions::<Repo>(
+3 -4
View File
@@ -1,5 +1,4 @@
use anyhow::anyhow;
use derive_variants::ExtractVariant as _;
use komodo_client::{
api::write::{UpdateResourceMeta, UpdateResourceMetaResponse},
entities::{
@@ -9,9 +8,9 @@ use komodo_client::{
sync::ResourceSync,
},
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::resource::{self, ResourceMetaUpdate};
@@ -33,7 +32,7 @@ impl Resolve<WriteArgs> for UpdateResourceMeta {
async fn resolve(
self,
args: &WriteArgs,
) -> serror::Result<UpdateResourceMetaResponse> {
) -> mogh_error::Result<UpdateResourceMetaResponse> {
let meta = ResourceMetaUpdate {
description: self.description,
template: self.template,
+7 -7
View File
@@ -10,8 +10,8 @@ use komodo_client::{
update::{Update, UpdateStatus},
},
};
use mogh_resolver::Resolve;
use periphery_client::api;
use resolver_api::Resolve;
use crate::{
helpers::{
@@ -37,7 +37,7 @@ impl Resolve<WriteArgs> for CreateServer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Server> {
) -> mogh_error::Result<Server> {
resource::create::<Server>(
&self.name,
self.config,
@@ -64,7 +64,7 @@ impl Resolve<WriteArgs> for CopyServer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Server> {
) -> mogh_error::Result<Server> {
let Server { config, .. } = get_check_permissions::<Server>(
&self.id,
user,
@@ -97,7 +97,7 @@ impl Resolve<WriteArgs> for DeleteServer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Server> {
) -> mogh_error::Result<Server> {
Ok(resource::delete::<Server>(&self.id, user).await?)
}
}
@@ -115,7 +115,7 @@ impl Resolve<WriteArgs> for UpdateServer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Server> {
) -> mogh_error::Result<Server> {
Ok(resource::update::<Server>(&self.id, self.config, user).await?)
}
}
@@ -133,7 +133,7 @@ impl Resolve<WriteArgs> for RenameServer {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Server>(&self.id, &self.name, user).await?)
}
}
@@ -151,7 +151,7 @@ impl Resolve<WriteArgs> for CreateNetwork {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let server = get_check_permissions::<Server>(
&self.server,
user,
+22 -34
View File
@@ -1,19 +1,20 @@
use anyhow::{Context, anyhow};
use database::mungos::{by_id::find_one_by_id, mongodb::bson::doc};
use komodo_client::{
api::{user::CreateApiKey, write::*},
api::write::*,
entities::{
komodo_timestamp,
user::{User, UserConfig},
user::{NewUserParams, User, UserConfig},
},
};
use mogh_auth_client::api::manage::CreateApiKey;
use mogh_auth_server::api::manage::api_key::create_api_key;
use mogh_error::{AddStatusCode as _, AddStatusCodeError as _};
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::{AddStatusCode as _, AddStatusCodeError as _};
use crate::{
api::user::UserArgs,
helpers::validations::{validate_api_key_name, validate_username},
auth::KomodoAuthImpl, helpers::validations::validate_username,
state::db_client,
};
@@ -32,7 +33,7 @@ impl Resolve<WriteArgs> for CreateServiceUser {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CreateServiceUserResponse> {
) -> mogh_error::Result<CreateServiceUserResponse> {
if !user.admin {
return Err(
anyhow!("Only Admins can manage Service Users")
@@ -47,24 +48,14 @@ impl Resolve<WriteArgs> for CreateServiceUser {
description: self.description,
};
let mut user = User {
id: Default::default(),
let mut user = User::new(NewUserParams {
username: self.username,
config,
totp: Default::default(),
passkey: Default::default(),
third_party_skip_2fa: Default::default(),
enabled: true,
admin: false,
super_admin: false,
create_server_permissions: false,
create_build_permissions: false,
last_update_view: 0,
linked_logins: Default::default(),
recents: Default::default(),
all: Default::default(),
config,
updated_at: komodo_timestamp(),
};
});
user.id = db_client()
.users
@@ -93,7 +84,7 @@ impl Resolve<WriteArgs> for UpdateServiceUserDescription {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<UpdateServiceUserDescriptionResponse> {
) -> mogh_error::Result<UpdateServiceUserDescriptionResponse> {
if !user.admin {
return Err(
anyhow!("Only Admins can manage Service Users")
@@ -150,7 +141,7 @@ impl Resolve<WriteArgs> for CreateApiKeyForServiceUser {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<CreateApiKeyForServiceUserResponse> {
) -> mogh_error::Result<CreateApiKeyForServiceUserResponse> {
if !user.admin {
return Err(
anyhow!("Only Admins can manage Service Users")
@@ -158,9 +149,6 @@ impl Resolve<WriteArgs> for CreateApiKeyForServiceUser {
);
}
validate_api_key_name(&self.name)
.status_code(StatusCode::BAD_REQUEST)?;
let service_user =
find_one_by_id(&db_client().users, &self.user_id)
.await
@@ -174,14 +162,14 @@ impl Resolve<WriteArgs> for CreateApiKeyForServiceUser {
);
};
CreateApiKey {
name: self.name,
expires: self.expires,
}
.resolve(&UserArgs {
user: service_user,
session: None,
})
create_api_key(
&KomodoAuthImpl,
service_user.id,
CreateApiKey {
name: self.name,
expires: self.expires as u64,
},
)
.await
}
}
@@ -198,7 +186,7 @@ impl Resolve<WriteArgs> for DeleteApiKeyForServiceUser {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<DeleteApiKeyForServiceUserResponse> {
) -> mogh_error::Result<DeleteApiKeyForServiceUserResponse> {
if !user.admin {
return Err(
anyhow!("Only Admins can manage Service Users")
+523 -33
View File
@@ -1,40 +1,55 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use anyhow::{Context, anyhow};
use database::mungos::mongodb::bson::{doc, to_document};
use formatting::format_serror;
use komodo_client::{
api::write::*,
entities::{
FileContents, NoData, Operation, RepoExecutionArgs,
all_logs_success,
permission::PermissionLevel,
repo::Repo,
stack::{Stack, StackInfo},
update::Update,
user::stack_user,
use database::{
bson::to_bson,
mungos::{
by_id::update_one_by_id,
mongodb::bson::{doc, to_document},
},
};
use formatting::format_serror;
use futures_util::{StreamExt as _, stream::FuturesOrdered};
use komodo_client::{
api::{execute::DeployStack, write::*},
entities::{
FileContents, NoData, Operation, RepoExecutionArgs,
ResourceTarget, SwarmOrServer,
alert::{Alert, AlertData, SeverityLevel},
all_logs_success, komodo_timestamp,
permission::PermissionLevel,
repo::Repo,
stack::{Stack, StackInfo, StackServiceWithUpdate, StackState},
update::Update,
user::{auto_redeploy_user, stack_user, system_user},
},
};
use mogh_cache::SetCache;
use mogh_resolver::Resolve;
use periphery_client::api::compose::{
GetComposeContentsOnHost, GetComposeContentsOnHostResponse,
WriteComposeContentsToHost,
};
use resolver_api::Resolve;
use crate::{
alert::send_alerts,
api::execute::{self, ExecuteRequest, ExecutionResult},
config::core_config,
helpers::{
query::get_swarm_or_server,
stack_git_token, swarm_or_server_request,
update::{add_update, make_update},
update::{add_update, make_update, poll_update_until_complete},
},
permission::get_check_permissions,
resource,
resource::{self, list_full_for_user_using_pattern},
stack::{
remote::{RemoteComposeContents, get_repo_compose_contents},
services::extract_services_into_res,
services::{
extract_services_from_stack, extract_services_into_res,
},
setup_stack_execution,
},
state::db_client,
state::{db_client, image_digest_cache, stack_status_cache},
};
use super::WriteArgs;
@@ -52,7 +67,7 @@ impl Resolve<WriteArgs> for CreateStack {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Stack> {
) -> mogh_error::Result<Stack> {
resource::create::<Stack>(&self.name, self.config, None, user)
.await
}
@@ -71,7 +86,7 @@ impl Resolve<WriteArgs> for CopyStack {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Stack> {
) -> mogh_error::Result<Stack> {
let Stack { config, .. } = get_check_permissions::<Stack>(
&self.id,
user,
@@ -96,7 +111,7 @@ impl Resolve<WriteArgs> for DeleteStack {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Stack> {
) -> mogh_error::Result<Stack> {
Ok(resource::delete::<Stack>(&self.id, user).await?)
}
}
@@ -114,8 +129,31 @@ impl Resolve<WriteArgs> for UpdateStack {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Stack> {
Ok(resource::update::<Stack>(&self.id, self.config, user).await?)
) -> mogh_error::Result<Stack> {
let compose_update = self.config.linked_repo.is_some()
|| self.config.repo.is_some()
|| self.config.files_on_host.is_some()
|| self.config.file_contents.is_some();
let stack =
resource::update::<Stack>(&self.id, self.config, user).await?;
if compose_update {
tokio::spawn(async move {
let _ = (CheckStackForUpdate {
stack: self.id,
skip_auto_update: false,
wait_for_auto_update: false,
skip_cache_refresh: true,
})
.resolve(&WriteArgs {
user: system_user().to_owned(),
})
.await;
});
}
Ok(stack)
}
}
@@ -132,7 +170,7 @@ impl Resolve<WriteArgs> for RenameStack {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Stack>(&self.id, &self.name, user).await?)
}
}
@@ -150,7 +188,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let WriteStackFileContents {
stack,
file_path,
@@ -177,11 +215,13 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
update.push_simple_log("File contents to write", &contents);
if stack.config.files_on_host {
let id = stack.id.clone();
let update = if stack.config.files_on_host {
write_stack_file_contents_on_host(
stack, file_path, contents, update,
)
.await
.await?
} else {
write_stack_file_contents_git(
stack,
@@ -190,8 +230,23 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
&user.username,
update,
)
.await
}
.await?
};
tokio::spawn(async move {
let _ = (CheckStackForUpdate {
stack: id,
skip_auto_update: false,
wait_for_auto_update: false,
skip_cache_refresh: true,
})
.resolve(&WriteArgs {
user: system_user().to_owned(),
})
.await;
});
Ok(update)
}
}
@@ -201,7 +256,7 @@ async fn write_stack_file_contents_on_host(
file_path: String,
contents: String,
mut update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let swarm_or_server = get_swarm_or_server(
&stack.config.swarm_id,
&stack.config.server_id,
@@ -267,7 +322,7 @@ async fn write_stack_file_contents_git(
contents: &str,
username: &str,
mut update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let mut repo = if !stack.config.linked_repo.is_empty() {
crate::resource::get::<Repo>(&stack.config.linked_repo)
.await?
@@ -405,7 +460,7 @@ impl Resolve<WriteArgs> for RefreshStackCache {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<NoData> {
) -> mogh_error::Result<NoData> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// stack should be able to do this.
let stack = get_check_permissions::<Stack>(
@@ -438,6 +493,15 @@ impl Resolve<WriteArgs> for RefreshStackCache {
}
let mut missing_files = Vec::new();
let service_image_digests = stack
.info
.latest_services
.iter()
.filter_map(|s| {
let digest = s.image_digest.clone()?;
Some((s.service_name.clone(), digest))
})
.collect::<HashMap<_, _>>();
let (
latest_services,
@@ -487,6 +551,7 @@ impl Resolve<WriteArgs> for RefreshStackCache {
if let Err(e) = extract_services_into_res(
&project_name,
&contents.contents,
&service_image_digests,
&mut services,
) {
warn!(
@@ -498,7 +563,16 @@ impl Resolve<WriteArgs> for RefreshStackCache {
(services, Some(contents), Some(errors), None, None)
} else {
(vec![], None, None, None, None)
// 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,
)
}
} else if !repo_empty {
// ================
@@ -529,6 +603,7 @@ impl Resolve<WriteArgs> for RefreshStackCache {
if let Err(e) = extract_services_into_res(
&project_name,
&contents.contents,
&service_image_digests,
&mut services,
) {
warn!(
@@ -554,6 +629,7 @@ impl Resolve<WriteArgs> for RefreshStackCache {
// this should latest (not deployed), so make the project name fresh.
&stack.project_name(true),
&stack.config.file_contents,
&service_image_digests,
&mut services,
) {
warn!(
@@ -595,3 +671,417 @@ impl Resolve<WriteArgs> for RefreshStackCache {
Ok(NoData {})
}
}
//
impl Resolve<WriteArgs> for CheckStackForUpdate {
#[instrument(
"CheckStackForUpdate",
skip_all,
fields(
operator = user.id,
stack = self.stack,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> mogh_error::Result<Self::Response> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// stack should be able to do this.
let (stack, swarm_or_server) = setup_stack_execution(
&self.stack,
user,
PermissionLevel::Execute.into(),
)
.await?;
swarm_or_server.verify_has_target()?;
check_stack_for_update_inner(
stack.id,
&swarm_or_server,
self.skip_auto_update,
self.wait_for_auto_update,
self.skip_cache_refresh,
)
.await
.map_err(Into::into)
}
}
/// If it goes down the "update available" path,
/// only send alert if stack id + service is not in this cache.
/// If alert is sent, add ID to cache.
/// If later it goes down non "update available" path,
/// remove the id from cache, so next time it does another alert
/// will be sent.
fn stack_alert_sent_cache() -> &'static SetCache<(String, String)> {
static CACHE: OnceLock<SetCache<(String, String)>> =
OnceLock::new();
CACHE.get_or_init(Default::default)
}
/// First refresh stack cache, then save
/// latest available 'image_digest' for stack services
/// to database.
#[instrument(
"CheckStackForUpdateInner",
skip_all,
fields(
stack = stack,
)
)]
pub async fn check_stack_for_update_inner(
// ID or name.
stack: String,
swarm_or_server: &SwarmOrServer,
skip_auto_update: bool,
// Otherwise spawns task to run in background
wait_for_auto_update: bool,
skip_cache_refresh: bool,
) -> anyhow::Result<CheckStackForUpdateResponse> {
if !skip_cache_refresh {
(RefreshStackCache {
stack: stack.clone(),
})
.resolve(&WriteArgs {
user: system_user().to_owned(),
})
.await
.map_err(|e| e.error)
.context("Failed to refresh stack cache before update check")?;
}
// Query again after refresh
let mut stack = resource::get::<Stack>(&stack).await?;
let cache = image_digest_cache();
for service in &mut stack.info.latest_services {
// Prefer the image coming from deployed services
// so it will be after any interpolation.
let image = stack
.info
.deployed_services
.as_ref()
.and_then(|services| {
services.iter().find_map(|deployed| {
(deployed.service_name == service.service_name)
.then_some(&deployed.image)
})
})
.unwrap_or(&service.image);
if image.is_empty() ||
// Images with a hardcoded digest can't have update.
image.contains('@')
{
service.image_digest = None;
continue;
}
match cache.get(swarm_or_server, image, None, None).await {
Ok(digest) => service.image_digest = Some(digest),
Err(e) => {
warn!(
"Failed to check for update | Stack: {} | Service: {} | Error: {e:#}",
stack.name, service.service_name
);
service.image_digest = None;
continue;
}
};
}
let latest_services = to_bson(&stack.info.latest_services)
.context("Failed to serialize stack latest services to BSON")?;
update_one_by_id(
&db_client().stacks,
&stack.id,
doc! { "$set": { "info.latest_services": latest_services } },
None,
)
.await?;
let alert_cache = stack_alert_sent_cache();
let Some(status) = stack_status_cache().get(&stack.id).await else {
alert_cache
.retain(|(stack_id, _)| stack_id != &stack.id)
.await;
return Ok(CheckStackForUpdateResponse {
services: extract_services_from_stack(&stack)
.into_iter()
.map(|service| StackServiceWithUpdate {
service: service.service_name,
image: service.image,
update_available: false,
})
.collect(),
stack: stack.id,
});
};
let StackState::Running = status.curr.state else {
alert_cache
.retain(|(stack_id, _)| stack_id != &stack.id)
.await;
return Ok(CheckStackForUpdateResponse {
stack: stack.id,
services: status
.curr
.services
.iter()
.map(|service| StackServiceWithUpdate {
service: service.service.clone(),
image: service.image.clone(),
update_available: false,
})
.collect(),
});
};
let mut services = Vec::new();
for service in status.curr.services.iter() {
let mut service_with_update = StackServiceWithUpdate {
service: service.service.clone(),
image: service.image.clone(),
update_available: false,
};
let Some(current_digests) = &service.image_digests else {
services.push(service_with_update);
continue;
};
let Some(latest_digest) =
stack.info.latest_services.iter().find_map(|s| {
if s.service_name == service.service {
s.image_digest.clone()
} else {
None
}
})
else {
services.push(service_with_update);
continue;
};
service_with_update.update_available =
latest_digest.update_available(current_digests);
if service_with_update.update_available
&& (skip_auto_update || !stack.config.auto_update)
&& !alert_cache
.contains(&(stack.id.clone(), service.service.clone()))
.await
{
// Send service update available alert
alert_cache
.insert((stack.id.clone(), service.service.clone()))
.await;
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Stack(stack.id.clone()),
data: AlertData::StackImageUpdateAvailable {
id: stack.id.clone(),
name: stack.name.clone(),
swarm_name: swarm_or_server
.swarm_name()
.map(str::to_string),
swarm_id: swarm_or_server.swarm_id().map(str::to_string),
server_name: swarm_or_server
.server_name()
.map(str::to_string),
server_id: swarm_or_server.server_id().map(str::to_string),
service: service.service.clone(),
image: service.image.clone(),
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!(
"Failed to record StackImageUpdateAvailable to db | {e:#}"
);
}
send_alerts(&[alert]).await;
}
services.push(service_with_update);
}
let services_with_update = services
.iter()
.filter(|service| service.update_available)
.cloned()
.collect::<Vec<_>>();
if skip_auto_update
|| !stack.config.auto_update
|| services_with_update.is_empty()
{
return Ok(CheckStackForUpdateResponse {
stack: stack.id,
services,
});
}
// Conservatively remove from alert cache so 'skip_auto_update'
// doesn't cause alerts not to be sent on subsequent calls.
alert_cache
.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()
{
Vec::new()
} else {
services_with_update
.iter()
.map(|service| service.service.clone())
.collect()
};
let swarm_id = swarm_or_server.swarm_id().map(str::to_string);
let swarm_name = swarm_or_server.swarm_name().map(str::to_string);
let server_id = swarm_or_server.server_id().map(str::to_string);
let server_name = swarm_or_server.server_name().map(str::to_string);
let stack_id = stack.id.clone();
let run = async move {
match execute::inner_handler(
ExecuteRequest::DeployStack(DeployStack {
stack: stack.id.clone(),
services: deploy_services,
stop_time: None,
}),
auto_redeploy_user().to_owned(),
)
.await
{
Ok(res) => {
let ExecutionResult::Single(update) = res else {
unreachable!()
};
let Ok(update) = poll_update_until_complete(&update.id).await
else {
return;
};
if update.success {
let ts = komodo_timestamp();
let alert = Alert {
id: Default::default(),
ts,
resolved: true,
resolved_ts: ts.into(),
level: SeverityLevel::Ok,
target: ResourceTarget::Stack(stack.id.clone()),
data: AlertData::StackAutoUpdated {
id: stack.id.clone(),
name: stack.name.clone(),
swarm_id,
swarm_name,
server_id,
server_name,
images: services_with_update
.iter()
.map(|service| service.image.clone())
.collect(),
},
};
let res = db_client().alerts.insert_one(&alert).await;
if let Err(e) = res {
error!("Failed to record StackAutoUpdated to db | {e:#}");
}
send_alerts(&[alert]).await;
}
}
Err(e) => {
warn!("Failed to auto update Stack {} | {e:#}", stack.name)
}
}
};
if wait_for_auto_update {
run.await
} else {
tokio::spawn(run);
}
Ok(CheckStackForUpdateResponse {
stack: stack_id,
services,
})
}
//
impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
#[instrument(
"BatchCheckStackForUpdate",
skip_all,
fields(
operator = user.id,
pattern = self.pattern,
skip_auto_update = self.skip_auto_update,
wait_for_auto_update = self.wait_for_auto_update,
)
)]
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> Result<Self::Response, Self::Error> {
let stacks = list_full_for_user_using_pattern::<Stack>(
&self.pattern,
Default::default(),
user,
PermissionLevel::Execute.into(),
&[],
)
.await?;
let res = stacks
.into_iter()
.map(|stack| async move {
let swarm_or_server = get_swarm_or_server(
&stack.config.swarm_id,
&stack.config.server_id,
)
.await?;
swarm_or_server.verify_has_target().map_err(|e| e.error)?;
check_stack_for_update_inner(
stack.id,
&swarm_or_server,
self.skip_auto_update,
self.wait_for_auto_update,
self.skip_cache_refresh,
)
.await
})
.collect::<FuturesOrdered<_>>()
.collect::<Vec<_>>()
.await
.into_iter()
.filter_map(|res| {
res
.inspect_err(|e| {
warn!(
"Failed to check stack for update in batch run | {e:#}"
)
})
.ok()
})
.collect();
Ok(res)
}
}
+6 -6
View File
@@ -4,7 +4,7 @@ use komodo_client::{
permission::PermissionLevel, swarm::Swarm, update::Update,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use crate::{permission::get_check_permissions, resource};
@@ -23,7 +23,7 @@ impl Resolve<WriteArgs> for CreateSwarm {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
) -> mogh_error::Result<Swarm> {
resource::create::<Swarm>(&self.name, self.config, None, user)
.await
}
@@ -42,7 +42,7 @@ impl Resolve<WriteArgs> for CopySwarm {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
) -> mogh_error::Result<Swarm> {
let Swarm { config, .. } = get_check_permissions::<Swarm>(
&self.id,
user,
@@ -66,7 +66,7 @@ impl Resolve<WriteArgs> for DeleteSwarm {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
) -> mogh_error::Result<Swarm> {
Ok(resource::delete::<Swarm>(&self.id, user).await?)
}
}
@@ -84,7 +84,7 @@ impl Resolve<WriteArgs> for UpdateSwarm {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Swarm> {
) -> mogh_error::Result<Swarm> {
Ok(resource::update::<Swarm>(&self.id, self.config, user).await?)
}
}
@@ -102,7 +102,7 @@ impl Resolve<WriteArgs> for RenameSwarm {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(resource::rename::<Swarm>(&self.id, &self.name, user).await?)
}
}
+71 -123
View File
@@ -26,13 +26,14 @@ use komodo_client::{
repo::Repo,
server::Server,
stack::Stack,
sync::{ResourceSync, ResourceSyncInfo, SyncDeployUpdate},
swarm::Swarm,
sync::{ResourceSync, ResourceSyncInfo},
to_path_compatible_name,
update::{Log, Update},
user::sync_user,
},
};
use resolver_api::Resolve;
use mogh_resolver::Resolve;
use tracing::Instrument;
use crate::{
@@ -69,7 +70,7 @@ impl Resolve<WriteArgs> for CreateResourceSync {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
resource::create::<ResourceSync>(
&self.name,
self.config,
@@ -93,7 +94,7 @@ impl Resolve<WriteArgs> for CopyResourceSync {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
let ResourceSync { config, .. } =
get_check_permissions::<ResourceSync>(
&self.id,
@@ -123,7 +124,7 @@ impl Resolve<WriteArgs> for DeleteResourceSync {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
Ok(resource::delete::<ResourceSync>(&self.id, user).await?)
}
}
@@ -141,7 +142,7 @@ impl Resolve<WriteArgs> for UpdateResourceSync {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
Ok(
resource::update::<ResourceSync>(&self.id, self.config, user)
.await?,
@@ -162,7 +163,7 @@ impl Resolve<WriteArgs> for RenameResourceSync {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
Ok(
resource::rename::<ResourceSync>(&self.id, &self.name, user)
.await?,
@@ -181,7 +182,10 @@ impl Resolve<WriteArgs> for WriteSyncFileContents {
file_path = self.file_path,
)
)]
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
async fn resolve(
self,
args: &WriteArgs,
) -> mogh_error::Result<Update> {
let sync = get_check_permissions::<ResourceSync>(
&self.sync,
&args.user,
@@ -231,7 +235,7 @@ async fn write_sync_file_contents_on_host(
args: &WriteArgs,
sync: ResourceSync,
mut update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let WriteSyncFileContents {
sync: _,
resource_path,
@@ -249,7 +253,7 @@ async fn write_sync_file_contents_on_host(
.context("Invalid resource path")?;
let full_path = root.join(&resource_path).join(&file_path);
if let Err(e) = secret_file::write_async(&full_path, &contents)
if let Err(e) = mogh_secret_file::write_async(&full_path, &contents)
.await
.with_context(|| {
format!(
@@ -295,7 +299,7 @@ async fn write_sync_file_contents_git(
sync: ResourceSync,
repo: Option<Repo>,
mut update: Update,
) -> serror::Result<Update> {
) -> mogh_error::Result<Update> {
let WriteSyncFileContents {
sync: _,
resource_path,
@@ -448,7 +452,10 @@ impl Resolve<WriteArgs> for CommitSync {
sync = self.sync,
)
)]
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
async fn resolve(
self,
args: &WriteArgs,
) -> mogh_error::Result<Update> {
let WriteArgs { user } = args;
let sync = get_check_permissions::<entities::sync::ResourceSync>(
@@ -509,11 +516,20 @@ 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(),
@@ -535,12 +551,13 @@ impl Resolve<WriteArgs> for CommitSync {
.join(to_path_compatible_name(&sync.name))
.join(&resource_path);
let span = info_span!("CommitSyncOnHost");
if let Err(e) = secret_file::write_async(&file_path, &res.toml)
.instrument(span)
.await
.with_context(|| {
format!("Failed to write resource file to {file_path:?}",)
})
if let Err(e) =
mogh_secret_file::write_async(&file_path, &res.toml)
.instrument(span)
.await
.with_context(|| {
format!("Failed to write resource file to {file_path:?}",)
})
{
update.push_error_log(
"Write resource file",
@@ -677,7 +694,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<ResourceSync> {
) -> mogh_error::Result<ResourceSync> {
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
// sync should be able to do this.
let mut sync =
@@ -768,110 +785,40 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
let mut diffs = Vec::new();
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?;
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),
);
(diffs, deploy_updates)
} else {
(Vec::new(), SyncDeployUpdate::default())
Default::default()
};
let variable_updates = if sync.config.include_variables {
@@ -905,7 +852,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
let (
resource_updates,
deploy_updates,
(pending_deploys, pending_deploy_error),
variable_updates,
user_group_updates,
pending_error,
@@ -921,7 +868,7 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
};
let has_updates = !resource_updates.is_empty()
|| !deploy_updates.to_deploy == 0
|| !pending_deploys.is_empty()
|| !variable_updates.is_empty()
|| !user_group_updates.is_empty();
@@ -933,7 +880,8 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
remote_errors: sync.info.remote_errors,
pending_hash: sync.info.pending_hash,
pending_message: sync.info.pending_message,
pending_deploy: deploy_updates,
pending_deploys,
pending_deploy_error,
resource_updates,
variable_updates,
user_group_updates,
+6 -6
View File
@@ -13,9 +13,9 @@ use komodo_client::{
server::Server, stack::Stack, sync::ResourceSync, tag::Tag,
},
};
use mogh_error::AddStatusCodeError;
use mogh_resolver::Resolve;
use reqwest::StatusCode;
use resolver_api::Resolve;
use serror::AddStatusCodeError;
use crate::{
config::core_config,
@@ -39,7 +39,7 @@ impl Resolve<WriteArgs> for CreateTag {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Tag> {
) -> mogh_error::Result<Tag> {
if core_config().disable_non_admin_create && !user.admin {
return Err(
anyhow!("Non admins cannot create tags")
@@ -88,7 +88,7 @@ impl Resolve<WriteArgs> for RenameTag {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Tag> {
) -> mogh_error::Result<Tag> {
if ObjectId::from_str(&self.name).is_ok() {
return Err(anyhow!("tag name cannot be ObjectId").into());
}
@@ -121,7 +121,7 @@ impl Resolve<WriteArgs> for UpdateTagColor {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Tag> {
) -> mogh_error::Result<Tag> {
let tag = get_tag_check_owner(&self.tag, user).await?;
update_one_by_id(
@@ -149,7 +149,7 @@ impl Resolve<WriteArgs> for DeleteTag {
async fn resolve(
self,
WriteArgs { user }: &WriteArgs,
) -> serror::Result<Tag> {
) -> mogh_error::Result<Tag> {
let tag = get_tag_check_owner(&self.id, user).await?;
tokio::try_join!(

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