mirror of
https://github.com/moghtech/komodo.git
synced 2026-08-09 07:46:53 -05:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97efa4808a | ||
|
|
8708f0f600 | ||
|
|
5c301020c1 | ||
|
|
f66754ef9e | ||
|
|
2aa8a441df | ||
|
|
1674ce2d51 | ||
|
|
0d462fb2e0 | ||
|
|
ca01407090 | ||
|
|
20b9d16d4b | ||
|
|
7c95823eb0 | ||
|
|
254622078e | ||
|
|
38421d6719 | ||
|
|
0f96794559 | ||
|
|
d79fefff17 | ||
|
|
4626b4a477 | ||
|
|
150a8a160d | ||
|
|
5b3bb9b8bb | ||
|
|
79831f29af | ||
|
|
b68f5abd25 | ||
|
|
2325899ebf | ||
|
|
23fd208f0f | ||
|
|
19904e00b9 | ||
|
|
8880b1c948 | ||
|
|
e47c8d2b62 | ||
|
|
1addb8fccd | ||
|
|
08e55b5180 |
+4
-1
@@ -1,2 +1,5 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
[build]
|
||||
rustflags = ["-Wunused-crate-dependencies"]
|
||||
rustflags = ["-Wunused-crate-dependencies"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Generated
+1911
-981
File diff suppressed because it is too large
Load Diff
+57
-53
@@ -3,21 +3,18 @@ resolver = "2"
|
||||
members = [
|
||||
"bin/*",
|
||||
"lib/*",
|
||||
"xtask",
|
||||
"client/core/rs",
|
||||
"client/periphery/rs",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.0.0-dev-123"
|
||||
version = "2.3.0"
|
||||
edition = "2024"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/moghtech/komodo"
|
||||
homepage = "https://komo.do"
|
||||
|
||||
[profile.release]
|
||||
strip = "debuginfo"
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
komodo_client = { path = "client/core/rs" }
|
||||
@@ -33,106 +30,113 @@ git = { path = "lib/git" }
|
||||
|
||||
# MOGH
|
||||
slack = { version = "2.0.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
mogh_error = { version = "1.0.3", default-features = false }
|
||||
mogh_error = { version = "1.0.4", default-features = false }
|
||||
derive_default_builder = "0.1.8"
|
||||
async_timing_util = "1.1.0"
|
||||
mogh_auth_client = "1.2.2"
|
||||
mogh_auth_server = "1.2.11"
|
||||
mogh_auth_client = "1.7.0"
|
||||
mogh_auth_server = "1.7.0"
|
||||
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"
|
||||
partial_derive2 = "0.5.0"
|
||||
mongo_indexed = "2.1.0"
|
||||
mogh_resolver = "1.0.0"
|
||||
mogh_config = "1.0.2"
|
||||
mogh_logger = "1.3.1"
|
||||
mogh_server = "1.4.5"
|
||||
mogh_config = "1.1.0"
|
||||
mogh_logger = "1.3.4"
|
||||
mogh_server = "1.5.0"
|
||||
toml_pretty = "2.0.0"
|
||||
mogh_cache = "1.1.1"
|
||||
mogh_pki = "1.1.0"
|
||||
mungos = "3.2.2"
|
||||
mogh_cache = "1.1.2"
|
||||
mogh_pki = "1.1.3"
|
||||
mungos = "3.7.0"
|
||||
svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
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"] }
|
||||
reqwest = { version = "0.13.4", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.19", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.19", features = ["sync"] }
|
||||
pin-project-lite = "0.2.17"
|
||||
futures-util = "0.3.32"
|
||||
arc-swap = "1.8.2"
|
||||
futures-util = "0.3.33"
|
||||
arc-swap = "1.9.2"
|
||||
|
||||
# SERVER
|
||||
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
|
||||
axum = { version = "0.8.8", features = ["ws", "json", "macros"] }
|
||||
axum-extra = { version = "0.12.5", features = ["typed-header"] }
|
||||
tokio-tungstenite = { version = "0.30.0", features = ["rustls-tls-native-roots"] }
|
||||
axum = { version = "0.8.9", features = ["ws", "json", "macros"] }
|
||||
axum-extra = { version = "0.12.6", features = ["typed-header"] }
|
||||
|
||||
# OPENAPI
|
||||
utoipa-scalar = { version = "0.3.0", features = ["axum"] }
|
||||
utoipa = "5.4.0"
|
||||
utoipa = "5.5.0"
|
||||
|
||||
# SER/DE
|
||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||
serde = { version = "1.0.227", features = ["derive"] }
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
bson = { version = "2.15.0" } # must keep in sync with mongodb version
|
||||
toml = "1.0.4"
|
||||
toml = "1.1.3"
|
||||
serde_yaml_ng = "0.10.0"
|
||||
serde_json = "1.0.149"
|
||||
serde_qs = "1.0.0"
|
||||
serde_json = "1.0.151"
|
||||
serde_qs = "1.1.2"
|
||||
url = "2.5.8"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.102"
|
||||
thiserror = "2.0.18"
|
||||
anyhow = "1.0.104"
|
||||
thiserror = "2.0.19"
|
||||
|
||||
# SYSTEM
|
||||
libc = "0.2.189"
|
||||
|
||||
# LOGGING
|
||||
tracing = "0.1.44"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.60", features = ["derive"] }
|
||||
clap = { version = "4.6.4", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
uuid = { version = "1.21.0", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.10.0"
|
||||
uuid = { version = "1.24.0", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.42", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.11.0"
|
||||
urlencoding = "2.1.3"
|
||||
bcrypt = "0.19.0"
|
||||
hmac = "0.12.1"
|
||||
sha1 = "0.10.6"
|
||||
sha2 = "0.10.9"
|
||||
rand = "0.10.0"
|
||||
bcrypt = "0.19.2"
|
||||
hmac = "0.13.0"
|
||||
sha1 = "0.11.0"
|
||||
sha2 = "0.11.0"
|
||||
rand = "0.10.2"
|
||||
hex = "0.4.3"
|
||||
|
||||
# SYSTEM
|
||||
hickory-resolver = "0.25.2"
|
||||
hickory-resolver = "0.26.1"
|
||||
portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.20.1"
|
||||
sysinfo = "0.38.3"
|
||||
shlex = "1.3.0"
|
||||
bollard = "0.21.0"
|
||||
sysinfo = "0.39.6"
|
||||
shlex = "2.0.1"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.8.15"
|
||||
aws-sdk-ec2 = "1.216.0"
|
||||
aws-credential-types = "1.2.14"
|
||||
aws-config = "1.10.0"
|
||||
aws-sdk-ec2 = "1.241.0"
|
||||
aws-credential-types = "1.3.0"
|
||||
|
||||
## CRON
|
||||
english-to-cron = "0.1.7"
|
||||
chrono-tz = "0.10.4"
|
||||
chrono = "0.4.44"
|
||||
chrono = "0.4.45"
|
||||
croner = "3.0.1"
|
||||
|
||||
# MISC
|
||||
async-compression = { version = "0.4.41", features = ["tokio", "gzip"] }
|
||||
async-compression = { version = "0.4.42", features = ["tokio", "gzip"] }
|
||||
schemars = { version = "1.2.1", features = ["indexmap2"] }
|
||||
derive_builder = "0.20.2"
|
||||
comfy-table = "7.2.2"
|
||||
typeshare = "1.0.5"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.1.1"
|
||||
bytes = "1.11.1"
|
||||
regex = "1.12.3"
|
||||
bytes = "1.12.1"
|
||||
regex = "1.13.1"
|
||||
|
||||
[profile.release]
|
||||
strip = "debuginfo"
|
||||
@@ -1,4 +1,4 @@
|
||||
const cmd = "km run -y action deploy-komodo-fe-change";
|
||||
const cmd = "km run -y action deploy-komodo-ui-change";
|
||||
new Deno.Command("bash", {
|
||||
args: ["-c", cmd],
|
||||
}).spawn();
|
||||
@@ -1,7 +1,7 @@
|
||||
## Builds the Komodo Core, Periphery, and Util binaries
|
||||
## for a specific architecture. Requires OpenSSL 3 or later.
|
||||
|
||||
FROM rust:1.93.1-bookworm AS builder
|
||||
FROM rust:1.97.1-bookworm AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
@@ -12,6 +12,7 @@ COPY ./client/periphery ./client/periphery
|
||||
COPY ./bin/core ./bin/core
|
||||
COPY ./bin/periphery ./bin/periphery
|
||||
COPY ./bin/cli ./bin/cli
|
||||
COPY ./xtask ./xtask
|
||||
|
||||
# Compile bin
|
||||
RUN \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## Uses chef for dependency caching to help speed up back-to-back builds.
|
||||
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.93.1-bookworm AS chef
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.97.1-bookworm AS chef
|
||||
WORKDIR /builder
|
||||
|
||||
# Plan just the RECIPE to see if things have changed
|
||||
|
||||
+2
-1
@@ -3,7 +3,6 @@ name = "komodo_cli"
|
||||
description = "Command line tool for Komodo"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -32,7 +31,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,4 +1,4 @@
|
||||
FROM rust:1.93.1-bullseye AS builder
|
||||
FROM rust:1.97.1-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@ ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64
|
||||
FROM ${X86_64_BINARIES} AS x86_64
|
||||
FROM ${AARCH64_BINARIES} AS aarch64
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,16 +7,16 @@ use futures_util::{
|
||||
FutureExt, TryStreamExt, stream::FuturesUnordered,
|
||||
};
|
||||
use komodo_client::{
|
||||
api::read::{
|
||||
InspectDockerContainer, ListAllDockerContainers, ListServers,
|
||||
},
|
||||
api::read::{self, ListAllContainers, ListServers},
|
||||
entities::{
|
||||
config::cli::args::container::{
|
||||
Container, ContainerCommand, InspectContainer,
|
||||
},
|
||||
docker::{
|
||||
self,
|
||||
container::{ContainerListItem, ContainerStateStatusEnum},
|
||||
container::{
|
||||
ContainerListItem, ContainerSortBy, ContainerStateStatusEnum,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -44,11 +44,12 @@ async fn list_containers(
|
||||
down,
|
||||
links,
|
||||
reverse,
|
||||
containers: names,
|
||||
containers: terms,
|
||||
images,
|
||||
networks,
|
||||
servers,
|
||||
format,
|
||||
page,
|
||||
command: _,
|
||||
}: &Container,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -60,9 +61,16 @@ async fn list_containers(
|
||||
.into_iter()
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllDockerContainers {
|
||||
client.read(ListAllContainers {
|
||||
servers: Default::default(),
|
||||
containers: Default::default(),
|
||||
tags: Default::default(),
|
||||
terms: terms.clone(),
|
||||
state: Default::default(),
|
||||
limit: Some(100),
|
||||
// Page is more naturally given starting as 1, 2, 3.
|
||||
page: if *page == 0 { 0 } else { *page - 1 },
|
||||
sort_by: ContainerSortBy::Server,
|
||||
sort_desc: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -78,7 +86,6 @@ async fn list_containers(
|
||||
(Some(server.name.as_str()), c)
|
||||
});
|
||||
|
||||
let names = parse_wildcards(names);
|
||||
let servers = parse_wildcards(servers);
|
||||
let images = parse_wildcards(images);
|
||||
let networks = parse_wildcards(networks);
|
||||
@@ -105,7 +112,6 @@ async fn list_containers(
|
||||
);
|
||||
state_check
|
||||
&& network_check
|
||||
&& matches_wildcards(&names, &[c.name.as_str()])
|
||||
&& matches_wildcards(
|
||||
&servers,
|
||||
&server_name
|
||||
@@ -145,9 +151,15 @@ pub async fn inspect_container(
|
||||
.into_iter()
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllDockerContainers {
|
||||
servers: Default::default(),
|
||||
containers: Default::default()
|
||||
client.read(ListAllContainers {
|
||||
servers: inspect.servers.clone(),
|
||||
tags: Default::default(),
|
||||
terms: vec![inspect.container.clone()],
|
||||
state: Default::default(),
|
||||
limit: Some(0),
|
||||
page: 0,
|
||||
sort_by: ContainerSortBy::Server,
|
||||
sort_desc: false,
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -162,25 +174,11 @@ pub async fn inspect_container(
|
||||
c.server_id = Some(server.name.clone());
|
||||
});
|
||||
|
||||
let names = [inspect.container.to_string()];
|
||||
let names = parse_wildcards(&names);
|
||||
let servers = parse_wildcards(&inspect.servers);
|
||||
|
||||
let mut containers = containers
|
||||
.into_iter()
|
||||
.filter(|c| {
|
||||
matches_wildcards(&names, &[c.name.as_str()])
|
||||
&& matches_wildcards(
|
||||
&servers,
|
||||
&c.server_id
|
||||
.as_deref()
|
||||
.map(|i| vec![i])
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.map(|c| async move {
|
||||
client
|
||||
.read(InspectDockerContainer {
|
||||
.read(read::InspectContainer {
|
||||
container: c.name,
|
||||
server: c.server_id.context("No server...")?,
|
||||
})
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
+133
-550
@@ -28,558 +28,141 @@ 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,
|
||||
CancelAction,
|
||||
RunProcedure,
|
||||
CancelProcedure,
|
||||
RunBuild,
|
||||
CancelBuild,
|
||||
Deploy,
|
||||
PullDeployment,
|
||||
StartDeployment,
|
||||
RestartDeployment,
|
||||
PauseDeployment,
|
||||
UnpauseDeployment,
|
||||
StopDeployment,
|
||||
DestroyDeployment,
|
||||
CloneRepo,
|
||||
PullRepo,
|
||||
BuildRepo,
|
||||
CancelRepoBuild,
|
||||
StartContainer,
|
||||
RestartContainer,
|
||||
PauseContainer,
|
||||
UnpauseContainer,
|
||||
StopContainer,
|
||||
DestroyContainer,
|
||||
StartAllContainers,
|
||||
RestartAllContainers,
|
||||
PauseAllContainers,
|
||||
UnpauseAllContainers,
|
||||
StopAllContainers,
|
||||
PruneContainers,
|
||||
DeleteNetwork,
|
||||
PruneNetworks,
|
||||
DeleteImage,
|
||||
PruneImages,
|
||||
DeleteVolume,
|
||||
PruneVolumes,
|
||||
PruneDockerBuilders,
|
||||
PruneBuildx,
|
||||
PruneSystem,
|
||||
RunSync,
|
||||
DeployStack,
|
||||
DeployStackIfChanged,
|
||||
PullStack,
|
||||
StartStack,
|
||||
RestartStack,
|
||||
PauseStack,
|
||||
UnpauseStack,
|
||||
StopStack,
|
||||
DestroyStack,
|
||||
RunStackService,
|
||||
TestAlerter,
|
||||
SendAlert,
|
||||
RemoveSwarmNodes,
|
||||
UpdateSwarmNode,
|
||||
RemoveSwarmStacks,
|
||||
RemoveSwarmServices,
|
||||
CreateSwarmConfig,
|
||||
RotateSwarmConfig,
|
||||
RemoveSwarmConfigs,
|
||||
CreateSwarmSecret,
|
||||
RotateSwarmSecret,
|
||||
RemoveSwarmSecrets,
|
||||
ClearRepoCache,
|
||||
BackupCoreDatabase,
|
||||
GlobalAutoUpdate,
|
||||
RotateAllServerKeys,
|
||||
RotateCoreKeys,
|
||||
],
|
||||
batch: [
|
||||
BatchRunAction,
|
||||
BatchRunProcedure,
|
||||
BatchRunBuild,
|
||||
BatchDeploy,
|
||||
BatchDestroyDeployment,
|
||||
BatchCloneRepo,
|
||||
BatchPullRepo,
|
||||
BatchBuildRepo,
|
||||
BatchDeployStack,
|
||||
BatchDeployStackIfChanged,
|
||||
BatchPullStack,
|
||||
BatchDestroyStack,
|
||||
],
|
||||
);
|
||||
|
||||
match res {
|
||||
Ok(ExecutionResult::Single(update)) => {
|
||||
|
||||
+193
-31
@@ -7,7 +7,8 @@ use komodo_client::{
|
||||
api::read::{
|
||||
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
||||
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
||||
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
|
||||
ListSchedules, ListServers, ListStacks, ListSwarms, ListTags,
|
||||
ListTerminals,
|
||||
},
|
||||
entities::{
|
||||
ResourceTargetVariant,
|
||||
@@ -31,6 +32,7 @@ use komodo_client::{
|
||||
schedule::Schedule,
|
||||
server::{ServerListItem, ServerListItemInfo, ServerState},
|
||||
stack::{StackListItem, StackListItemInfo, StackState},
|
||||
swarm::{SwarmListItem, SwarmListItemInfo, SwarmState},
|
||||
sync::{
|
||||
ResourceSyncListItem, ResourceSyncListItemInfo,
|
||||
ResourceSyncState,
|
||||
@@ -52,28 +54,46 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
||||
match &list.command {
|
||||
None => list_all(list).await,
|
||||
Some(ListCommand::Servers(filters)) => {
|
||||
list_resources::<ServerListItem>(filters, false).await
|
||||
list_resources::<ServerListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Swarms(filters)) => {
|
||||
list_resources::<SwarmListItem>(filters, list.page, false).await
|
||||
}
|
||||
Some(ListCommand::Stacks(filters)) => {
|
||||
list_resources::<StackListItem>(filters, false).await
|
||||
list_resources::<StackListItem>(filters, list.page, false).await
|
||||
}
|
||||
Some(ListCommand::Deployments(filters)) => {
|
||||
list_resources::<DeploymentListItem>(filters, false).await
|
||||
list_resources::<DeploymentListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Builds(filters)) => {
|
||||
list_resources::<BuildListItem>(filters, false).await
|
||||
list_resources::<BuildListItem>(filters, list.page, false).await
|
||||
}
|
||||
Some(ListCommand::Repos(filters)) => {
|
||||
list_resources::<RepoListItem>(filters, false).await
|
||||
list_resources::<RepoListItem>(filters, list.page, false).await
|
||||
}
|
||||
Some(ListCommand::Procedures(filters)) => {
|
||||
list_resources::<ProcedureListItem>(filters, false).await
|
||||
list_resources::<ProcedureListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Actions(filters)) => {
|
||||
list_resources::<ActionListItem>(filters, false).await
|
||||
list_resources::<ActionListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Syncs(filters)) => {
|
||||
list_resources::<ResourceSyncListItem>(filters, false).await
|
||||
list_resources::<ResourceSyncListItem>(
|
||||
filters, list.page, false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Builders(filters)) => {
|
||||
list_resources::<BuilderListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Alerters(filters)) => {
|
||||
list_resources::<AlerterListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Terminals(filters)) => {
|
||||
list_terminals(filters).await
|
||||
@@ -81,12 +101,6 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
||||
Some(ListCommand::Schedules(filters)) => {
|
||||
list_schedules(filters).await
|
||||
}
|
||||
Some(ListCommand::Builders(filters)) => {
|
||||
list_resources::<BuilderListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Alerters(filters)) => {
|
||||
list_resources::<AlerterListItem>(filters, false).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +111,7 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
let (
|
||||
tags,
|
||||
mut servers,
|
||||
mut swarms,
|
||||
mut stacks,
|
||||
mut deployments,
|
||||
mut builds,
|
||||
@@ -109,14 +124,15 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
ServerListItem::list(client, &filters, true),
|
||||
StackListItem::list(client, &filters, true),
|
||||
DeploymentListItem::list(client, &filters, true),
|
||||
BuildListItem::list(client, &filters, true),
|
||||
RepoListItem::list(client, &filters, true),
|
||||
ProcedureListItem::list(client, &filters, true),
|
||||
ActionListItem::list(client, &filters, true),
|
||||
ResourceSyncListItem::list(client, &filters, true),
|
||||
ServerListItem::list(client, &filters, list.page, true),
|
||||
SwarmListItem::list(client, &filters, list.page, true),
|
||||
StackListItem::list(client, &filters, list.page, true),
|
||||
DeploymentListItem::list(client, &filters, list.page, true),
|
||||
BuildListItem::list(client, &filters, list.page, true),
|
||||
RepoListItem::list(client, &filters, list.page, true),
|
||||
ProcedureListItem::list(client, &filters, list.page, true),
|
||||
ActionListItem::list(client, &filters, list.page, true),
|
||||
ResourceSyncListItem::list(client, &filters, list.page, true),
|
||||
)?;
|
||||
|
||||
if !servers.is_empty() {
|
||||
@@ -125,6 +141,12 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
println!();
|
||||
}
|
||||
|
||||
if !swarms.is_empty() {
|
||||
fix_tags(&mut swarms, &tags);
|
||||
print_items(swarms, filters.format, list.links)?;
|
||||
println!();
|
||||
}
|
||||
|
||||
if !stacks.is_empty() {
|
||||
fix_tags(&mut stacks, &tags);
|
||||
print_items(stacks, filters.format, list.links)?;
|
||||
@@ -172,6 +194,7 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
|
||||
async fn list_resources<T>(
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
@@ -180,7 +203,7 @@ where
|
||||
{
|
||||
let client = crate::command::komodo_client().await?;
|
||||
let (mut resources, tags) = tokio::try_join!(
|
||||
T::list(client, filters, minimal),
|
||||
T::list(client, filters, page, minimal),
|
||||
client.read(ListTags::default()).map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
@@ -205,6 +228,8 @@ async fn list_terminals(
|
||||
.read(ListTerminals {
|
||||
target: None,
|
||||
use_names: true,
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
if !terminals.is_empty() {
|
||||
@@ -222,6 +247,8 @@ async fn list_schedules(
|
||||
.read(ListSchedules {
|
||||
tags: filters.tags.clone(),
|
||||
tag_behavior: Default::default(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -279,6 +306,7 @@ where
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
// For use with root `km ls`
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
|
||||
@@ -291,6 +319,7 @@ impl ListResources for ServerListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let servers = client
|
||||
@@ -300,6 +329,10 @@ impl ListResources for ServerListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?;
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -329,17 +362,68 @@ impl ListResources for ServerListItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ListResources for SwarmListItem {
|
||||
type Info = SwarmListItemInfo;
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let servers = client
|
||||
.read(ListSwarms {
|
||||
query: ResourceQuery::builder()
|
||||
.tags(filters.tags.clone())
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?;
|
||||
let names = parse_wildcards(&filters.names);
|
||||
let server_wildcards = parse_wildcards(&filters.servers);
|
||||
let mut servers = servers
|
||||
.into_iter()
|
||||
.filter(|server| {
|
||||
let state_check = if filters.all {
|
||||
true
|
||||
} else if filters.down {
|
||||
!matches!(server.info.state, SwarmState::Healthy)
|
||||
} else if filters.in_progress {
|
||||
false
|
||||
} else {
|
||||
matches!(server.info.state, SwarmState::Healthy)
|
||||
};
|
||||
let name_items = &[server.name.as_str()];
|
||||
state_check
|
||||
&& matches_wildcards(&names, name_items)
|
||||
&& matches_wildcards(&server_wildcards, name_items)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
servers.sort_by(|a, b| {
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ListResources for StackListItem {
|
||||
type Info = StackListItemInfo;
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (servers, mut stacks) = tokio::try_join!(
|
||||
client
|
||||
.read(ListServers {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -351,6 +435,10 @@ impl ListResources for StackListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
stacks.iter_mut().for_each(|stack| {
|
||||
@@ -406,12 +494,15 @@ impl ListResources for DeploymentListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (servers, mut deployments) = tokio::try_join!(
|
||||
client
|
||||
.read(ListServers {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -423,6 +514,10 @@ impl ListResources for DeploymentListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
deployments.iter_mut().for_each(|deployment| {
|
||||
@@ -479,12 +574,15 @@ impl ListResources for BuildListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (builders, mut builds) = tokio::try_join!(
|
||||
client
|
||||
.read(ListBuilders {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -496,6 +594,10 @@ impl ListResources for BuildListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
builds.iter_mut().for_each(|build| {
|
||||
@@ -533,10 +635,7 @@ impl ListResources for BuildListItem {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
builds.sort_by(|a, b| {
|
||||
a.name
|
||||
.cmp(&b.name)
|
||||
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
||||
.then(a.info.state.cmp(&b.info.state))
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(builds)
|
||||
}
|
||||
@@ -547,6 +646,7 @@ impl ListResources for RepoListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -557,6 +657,10 @@ impl ListResources for RepoListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -595,6 +699,7 @@ impl ListResources for ProcedureListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -605,6 +710,10 @@ impl ListResources for ProcedureListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -632,7 +741,7 @@ impl ListResources for ProcedureListItem {
|
||||
(Some(a), Some(b)) => return a.cmp(&b),
|
||||
(None, None) => {}
|
||||
}
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(procedures)
|
||||
}
|
||||
@@ -643,6 +752,7 @@ impl ListResources for ActionListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -653,6 +763,10 @@ impl ListResources for ActionListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -680,7 +794,7 @@ impl ListResources for ActionListItem {
|
||||
(Some(a), Some(b)) => return a.cmp(&b),
|
||||
(None, None) => {}
|
||||
}
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(actions)
|
||||
}
|
||||
@@ -691,6 +805,7 @@ impl ListResources for ResourceSyncListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -701,6 +816,10 @@ impl ListResources for ResourceSyncListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -725,7 +844,7 @@ impl ListResources for ResourceSyncListItem {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
syncs.sort_by(|a, b| {
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(syncs)
|
||||
}
|
||||
@@ -736,6 +855,7 @@ impl ListResources for BuilderListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -746,6 +866,10 @@ impl ListResources for BuilderListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -768,6 +892,7 @@ impl ListResources for AlerterListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -778,6 +903,10 @@ impl ListResources for AlerterListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -832,6 +961,39 @@ impl PrintTable for ResourceListItem<ServerListItemInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for ResourceListItem<SwarmListItemInfo> {
|
||||
fn header(links: bool) -> &'static [&'static str] {
|
||||
if links {
|
||||
&["Swarm", "State", "Tags", "Link"]
|
||||
} else {
|
||||
&["Swarm", "State", "Tags"]
|
||||
}
|
||||
}
|
||||
fn row(self, links: bool) -> Vec<Cell> {
|
||||
let color = match self.info.state {
|
||||
SwarmState::Healthy => Color::Green,
|
||||
SwarmState::Unhealthy => Color::Red,
|
||||
SwarmState::Down => Color::Blue,
|
||||
SwarmState::Unknown => Color::Magenta,
|
||||
};
|
||||
let mut res = vec![
|
||||
Cell::new(self.name).add_attribute(Attribute::Bold),
|
||||
Cell::new(self.info.state.to_string())
|
||||
.fg(color)
|
||||
.add_attribute(Attribute::Bold),
|
||||
Cell::new(self.tags.join(", ")),
|
||||
];
|
||||
if links {
|
||||
res.push(Cell::new(resource_link(
|
||||
&cli_config().host,
|
||||
ResourceTargetVariant::Server,
|
||||
&self.id,
|
||||
)))
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for ResourceListItem<StackListItemInfo> {
|
||||
fn header(links: bool) -> &'static [&'static str] {
|
||||
if links {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::{Context, anyhow};
|
||||
use colored::Colorize;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
read::{ListAllDockerContainers, ListServers},
|
||||
read::{GetServer, ListAllContainers, 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
|
||||
}
|
||||
|
||||
@@ -112,6 +118,8 @@ async fn get_server(
|
||||
server: Option<String>,
|
||||
container: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
// Don't need to find server for terminal,
|
||||
// user provided it, early return.
|
||||
if let Some(server) = server {
|
||||
return Ok(server);
|
||||
}
|
||||
@@ -119,9 +127,15 @@ async fn get_server(
|
||||
let client = super::komodo_client().await?;
|
||||
|
||||
let mut containers = client
|
||||
.read(ListAllDockerContainers {
|
||||
.read(ListAllContainers {
|
||||
servers: Default::default(),
|
||||
containers: vec![container.to_string()],
|
||||
tags: Default::default(),
|
||||
terms: vec![container.to_string()],
|
||||
state: Default::default(),
|
||||
limit: Some(0),
|
||||
page: 0,
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -132,11 +146,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
|
||||
@@ -147,6 +164,8 @@ async fn get_server(
|
||||
let servers = client
|
||||
.read(ListServers {
|
||||
query: ServerQuery::builder().names(servers).build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -163,6 +182,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 +267,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 +327,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")?;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -12,6 +12,9 @@ mod config;
|
||||
|
||||
async fn app() -> anyhow::Result<()> {
|
||||
dotenvy::dotenv().ok();
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.install_default()
|
||||
.expect("Failed to install default crypto provider");
|
||||
mogh_logger::init(&config::cli_config().cli_logging)?;
|
||||
let args = config::cli_args();
|
||||
let env = config::cli_env();
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
name = "komodo_core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.93.1-trixie AS core-builder
|
||||
FROM rust:1.97.1-trixie AS core-builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
@@ -11,6 +11,7 @@ COPY ./client/core/rs ./client/core/rs
|
||||
COPY ./client/periphery ./client/periphery
|
||||
COPY ./bin/core ./bin/core
|
||||
COPY ./bin/cli ./bin/cli
|
||||
COPY ./xtask ./xtask
|
||||
|
||||
# Compile app
|
||||
RUN cargo build -p komodo_core --release && \
|
||||
@@ -58,7 +59,8 @@ ENV KOMODO_CLI_CONFIG_PATHS="/config"
|
||||
# This ensures any `komodo.cli.*` takes precedence over the Core `/config/*config.*`
|
||||
ENV KOMODO_CLI_CONFIG_KEYWORDS="*config.*,*komodo.cli*.*"
|
||||
|
||||
CMD [ "/bin/bash", "-c", "update-ca-certificates && core" ]
|
||||
ENTRYPOINT [ "entrypoint.sh" ]
|
||||
CMD [ "core" ]
|
||||
|
||||
# Label to prevent Komodo from stopping with StopAllContainers
|
||||
LABEL komodo.skip="true"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
## Sets up the necessary runtime container dependencies for Komodo Core.
|
||||
## Since theres no heavy build here, QEMU multi-arch builds are fine for this image.
|
||||
|
||||
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
|
||||
ARG UI_IMAGE=ghcr.io/moghtech/komodo-ui: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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile).
|
||||
## Sets up the necessary runtime container dependencies for Komodo Core.
|
||||
|
||||
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest
|
||||
ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:2
|
||||
|
||||
# This is required to work with COPY --from
|
||||
FROM ${BINARIES_IMAGE} AS binaries
|
||||
|
||||
@@ -20,12 +20,14 @@ pub async fn send_alert(
|
||||
let link = resource_link(ResourceTargetVariant::Swarm, id);
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!("{level} | Swarm **{name}** is now **healthy**\n{link}")
|
||||
format!(
|
||||
"{level} | Swarm **{name}** is now **healthy**\n{link}"
|
||||
)
|
||||
}
|
||||
SeverityLevel::Critical => {
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|e| format!("\n**error**: {e}"))
|
||||
.map(|e| format!("\n**error**: {e:#?}"))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"{level} | Swarm **{name}** is **unhealthy** ❌\n{link}{err}"
|
||||
@@ -34,28 +36,6 @@ pub async fn send_alert(
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AlertData::ServerVersionMismatch {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
server_version,
|
||||
core_version,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | **{name}**{region} | Periphery version now matches Core version ✅\n{link}"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | **{name}**{region} | Version mismatch detected ⚠️\nPeriphery: **{server_version}** | Core: **{core_version}**\n{link}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertData::ServerUnreachable {
|
||||
id,
|
||||
name,
|
||||
@@ -82,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,
|
||||
@@ -140,7 +142,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"📦 Deployment **{name}** is now **{to}**{target}\nprevious: **{from}**\n{link}"
|
||||
@@ -161,7 +163,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Deployment **{name}** has an update available{target}\nimage: **{image}**\n{link}"
|
||||
@@ -182,7 +184,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Deployment **{name}** was updated automatically ⏫{target}\nimage: **{image}**\n{link}"
|
||||
@@ -205,7 +207,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"🥞 Stack **{name}** is now {to}{target}\nprevious: **{from}**\n{link}"
|
||||
@@ -227,7 +229,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Stack **{name}** has an update available{target}\nservice: **{service}**\nimage: **{image}**\n{link}"
|
||||
@@ -251,7 +253,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: **{server}**")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Stack **{name}** was updated automatically ⏫{target}\n{images_label}: **{images}**\n{link}"
|
||||
|
||||
+32
-30
@@ -260,35 +260,15 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
SeverityLevel::Critical => {
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|e| format!("\nerror: {e}"))
|
||||
.map(|e| format!("\nerror: {e:#?}"))
|
||||
.unwrap_or_default();
|
||||
format!("{level} | Swarm {name} is unhealthy ❌\n{link}{err}")
|
||||
format!(
|
||||
"{level} | Swarm {name} is unhealthy ❌\n{link}{err}"
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AlertData::ServerVersionMismatch {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
server_version,
|
||||
core_version,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | {name}{region} | Periphery version now matches Core version ✅\n{link}"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | {name}{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}\n{link}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertData::ServerUnreachable {
|
||||
id,
|
||||
name,
|
||||
@@ -313,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,
|
||||
@@ -371,7 +373,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"📦Deployment {name} is now {to_state}{target}\nprevious: {from}\n{link}",
|
||||
@@ -392,7 +394,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Deployment {name} has an update available{target}\nimage: {image}\n{link}",
|
||||
@@ -413,7 +415,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Deployment {name} was updated automatically{target}\nimage: {image}\n{link}",
|
||||
@@ -436,7 +438,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"🥞 Stack {name} is now {to_state}{target}\nprevious: {from}\n{link}",
|
||||
@@ -458,7 +460,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Stack {name} has an update available{target}\nservice: {service}\nimage: {image}\n{link}",
|
||||
@@ -482,7 +484,7 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
} else if let Some(server) = server_name {
|
||||
format!("\nserver: {server}")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
format!(
|
||||
"⬆ Stack {name} was updated automatically ⏫{target}\n{images_label}: {images_str}\n{link}",
|
||||
|
||||
+36
-36
@@ -42,7 +42,7 @@ pub async fn send_alert(
|
||||
format!("{level} | Swarm *{name}* is *unhealthy* ❌");
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|e| format!("\nerror: {e}"))
|
||||
.map(|e| format!("\nerror: {e:#?}"))
|
||||
.unwrap_or_default();
|
||||
let blocks = vec![
|
||||
Block::header(level),
|
||||
@@ -59,35 +59,6 @@ pub async fn send_alert(
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AlertData::ServerVersionMismatch {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
server_version,
|
||||
core_version,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let text = match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
|
||||
)
|
||||
}
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::ServerUnreachable {
|
||||
id,
|
||||
name,
|
||||
@@ -129,6 +100,35 @@ pub async fn send_alert(
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AlertData::ServerVersionMismatch {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
server_version,
|
||||
core_version,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let text = match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
|
||||
)
|
||||
}
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(resource_link(
|
||||
ResourceTargetVariant::Server,
|
||||
id,
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::ServerCpu {
|
||||
id,
|
||||
name,
|
||||
@@ -289,7 +289,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
@@ -317,7 +317,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
@@ -345,7 +345,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
@@ -374,7 +374,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
@@ -402,7 +402,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
@@ -435,7 +435,7 @@ pub async fn send_alert(
|
||||
} else if let Some(server) = server_name {
|
||||
format!("server: *{server}*\n")
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
};
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
|
||||
@@ -4,23 +4,26 @@ use std::{
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_standard_command;
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::bson::to_document,
|
||||
use anyhow::Context as _;
|
||||
use command::{CommandOptions, run_komodo_standard_command};
|
||||
use database::{
|
||||
bson::doc,
|
||||
mungos::{by_id::update_one_by_id, mongodb::bson::to_document},
|
||||
};
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
api::execute::{BatchExecutionResponse, BatchRunAction, RunAction},
|
||||
api::execute::{
|
||||
BatchExecutionResponse, BatchRunAction, CancelAction, RunAction,
|
||||
},
|
||||
entities::{
|
||||
FileFormat, JsonObject,
|
||||
FileFormat, JsonObject, Operation,
|
||||
action::Action,
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
config::core::CoreConfig,
|
||||
komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
random_string,
|
||||
update::Update,
|
||||
update::{Update, UpdateStatus},
|
||||
user::action_user,
|
||||
},
|
||||
parsers::parse_key_value_list,
|
||||
@@ -34,6 +37,7 @@ use mogh_auth_server::api::manage::api_key::{
|
||||
use mogh_config::merge_objects;
|
||||
use mogh_resolver::Resolve;
|
||||
use tokio::fs;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
alert::send_alerts,
|
||||
@@ -46,7 +50,7 @@ use crate::{
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource::refresh_action_state_cache,
|
||||
state::{action_states, db_client},
|
||||
state::{action_cancel_cache, action_states, db_client},
|
||||
};
|
||||
|
||||
use super::ExecuteArgs;
|
||||
@@ -66,18 +70,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunAction>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunAction>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -87,7 +96,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,
|
||||
@@ -95,7 +104,11 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let mut action = get_check_permissions::<Action>(
|
||||
&self.action,
|
||||
@@ -112,7 +125,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure action not already busy before updating.
|
||||
let _action_guard = action_state.update_custom(
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.running += 1,
|
||||
|state| state.running -= 1,
|
||||
false,
|
||||
@@ -193,14 +206,20 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
""
|
||||
};
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
action_cancel_cache()
|
||||
.insert(update.id.clone(), cancel.clone())
|
||||
.await;
|
||||
|
||||
let mut res = run_komodo_standard_command(
|
||||
// Keep this stage name as is, the UI will find the latest update log by matching the stage name
|
||||
"Execute Action",
|
||||
None,
|
||||
format!(
|
||||
"deno run --allow-all{https_cert_flag}{reload} {}",
|
||||
path.display()
|
||||
),
|
||||
CommandOptions::default().cancel(cancel),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -214,7 +233,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
update.logs.push(res);
|
||||
update.finalize();
|
||||
|
||||
mogh_error::Ok(update)
|
||||
mogh_error::Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
@@ -227,7 +246,13 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
);
|
||||
};
|
||||
|
||||
let update = res?;
|
||||
action_cancel_cache().remove(&update.id).await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
res?;
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with update_update.
|
||||
@@ -455,3 +480,65 @@ fn parse_action_arguments(
|
||||
.context("Failed to parse Json to action args"),
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ExecuteArgs> for CancelAction {
|
||||
#[instrument(
|
||||
"CancelAction",
|
||||
skip_all,
|
||||
fields(
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
update_id = update.id,
|
||||
action = self.action,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let action = get_check_permissions::<Action>(
|
||||
&self.action,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let update_id = if let Some(update_id) = self.update_id
|
||||
&& !update_id.is_empty()
|
||||
{
|
||||
update_id
|
||||
} else {
|
||||
db_client()
|
||||
.updates
|
||||
.find_one(doc! {
|
||||
"target.type": "Action",
|
||||
"target.id": &action.id,
|
||||
"operation": Operation::RunAction.as_ref(),
|
||||
"status": UpdateStatus::InProgress.as_ref(),
|
||||
})
|
||||
.await?
|
||||
.context("No active run to cancel found")?
|
||||
.id
|
||||
};
|
||||
|
||||
action_cancel_cache()
|
||||
.get(&update_id)
|
||||
.await
|
||||
.context("Action run cancel token not found")?
|
||||
.cancel();
|
||||
|
||||
let mut update = update.clone();
|
||||
update.push_simple_log(
|
||||
"Cancel Triggered",
|
||||
"The action cancel has been triggered.",
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 +106,16 @@ 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(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -28,7 +28,7 @@ use komodo_client::{
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
all_logs_success,
|
||||
build::{Build, BuildConfig},
|
||||
builder::{Builder, BuilderConfig},
|
||||
builder::Builder,
|
||||
deployment::DeploymentState,
|
||||
komodo_timestamp, optional_string,
|
||||
permission::PermissionLevel,
|
||||
@@ -75,18 +75,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunBuild>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunBuild>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +101,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,7 +109,11 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let mut build = get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
@@ -144,7 +153,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure build not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
if build.config.auto_increment_version {
|
||||
@@ -171,9 +180,6 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
let builder =
|
||||
resource::get::<Builder>(&build.config.builder_id).await?;
|
||||
|
||||
let is_server_builder =
|
||||
matches!(&builder.config, BuilderConfig::Server(_));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let poll = async {
|
||||
loop {
|
||||
@@ -182,19 +188,13 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
id = cancel_recv.recv() => id?
|
||||
};
|
||||
if incoming_build_id == build_id {
|
||||
if is_server_builder {
|
||||
update.push_error_log("Cancel acknowledged", "Build cancellation is not possible on server builders at this time. Use an AWS builder to enable this feature.");
|
||||
} else {
|
||||
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
|
||||
}
|
||||
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
|
||||
update.finalize();
|
||||
let id = update.id.clone();
|
||||
if let Err(e) = update_update(update).await {
|
||||
warn!("Failed to modify Update {id} on db | {e:#}");
|
||||
}
|
||||
if !is_server_builder {
|
||||
cancel_clone.cancel();
|
||||
}
|
||||
cancel_clone.cancel();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
build.name.clone(),
|
||||
Some(build.config.version),
|
||||
builder,
|
||||
&mut update,
|
||||
Some(&mut update),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -269,11 +269,11 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
replacers: Default::default(),
|
||||
}) => res,
|
||||
_ = cancel.cancelled() => {
|
||||
debug!("Build cancelled during clone, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("user cancelled build during repo clone"));
|
||||
debug!("Build cancelled during repo clone, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("Build cancelled during repo clone"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("Builder cleaned up");
|
||||
debug!("Builder cleaned up");
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
@@ -319,7 +319,14 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
}) => res.context("Failed at call to Periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
info!("Build cancelled during build, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
|
||||
if let Err(e) = periphery.request(api::build::CancelBuild {
|
||||
id: build.id.clone()
|
||||
})
|
||||
.await
|
||||
.context("Failed to cancel build execution on Server") {
|
||||
update.push_error_log("Cancel Build", format_serror(&e.into()));
|
||||
}
|
||||
update.push_error_log("Build Cancelled", String::from("User cancelled build during image build step"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
@@ -369,6 +376,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
@@ -505,10 +516,10 @@ pub async fn validate_cancel_build(
|
||||
)?;
|
||||
|
||||
match (latest_build, latest_cancel) {
|
||||
(Some(build), Some(cancel)) => {
|
||||
if cancel.start_ts > build.start_ts {
|
||||
return Err(anyhow!("Build has already been cancelled"));
|
||||
}
|
||||
(Some(build), Some(cancel))
|
||||
if cancel.start_ts > build.start_ts =>
|
||||
{
|
||||
return Err(anyhow!("Build has already been cancelled"));
|
||||
}
|
||||
(None, _) => return Err(anyhow!("No build in progress")),
|
||||
_ => {}
|
||||
@@ -522,7 +533,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,7 +541,11 @@ impl Resolve<ExecuteArgs> for CancelBuild {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let build = get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
@@ -553,8 +568,8 @@ impl Resolve<ExecuteArgs> for CancelBuild {
|
||||
let mut update = update.clone();
|
||||
|
||||
update.push_simple_log(
|
||||
"cancel triggered",
|
||||
"the build cancel has been triggered",
|
||||
"Cancel Triggered",
|
||||
"The build cancel has been triggered",
|
||||
);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
@@ -609,7 +624,13 @@ async fn handle_post_build_redeploy(build_id: &str) {
|
||||
let state = get_deployment_state(&deployment.id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if state == DeploymentState::Running {
|
||||
if ![
|
||||
DeploymentState::NotDeployed,
|
||||
DeploymentState::Exited,
|
||||
DeploymentState::Unknown,
|
||||
]
|
||||
.contains(&state)
|
||||
{
|
||||
let req = super::ExecuteRequest::Deploy(Deploy {
|
||||
deployment: deployment.id.clone(),
|
||||
stop_signal: None,
|
||||
@@ -626,7 +647,7 @@ async fn handle_post_build_redeploy(build_id: &str) {
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -32,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,
|
||||
};
|
||||
@@ -55,18 +55,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeploy>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchDeploy>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +81,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,
|
||||
@@ -86,7 +91,11 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (mut deployment, swarm_or_server) =
|
||||
setup_deployment_execution(
|
||||
@@ -106,7 +115,7 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -118,26 +127,16 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
let (version, registry_token) = match &deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(build_id).await?;
|
||||
let image_names = build.get_image_names();
|
||||
let image_name = image_names
|
||||
.first()
|
||||
.context("No image name could be created")
|
||||
.context("Failed to create image name")?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
*version
|
||||
};
|
||||
let version_str = version.to_string();
|
||||
// Potentially add the build image_tag postfix
|
||||
let version_str = if build.config.image_tag.is_empty() {
|
||||
version_str
|
||||
} else {
|
||||
format!("{version_str}-{}", build.config.image_tag)
|
||||
};
|
||||
let image_name = build.get_deployment_image_name();
|
||||
let image_tag = build.get_deployment_image_tag(version);
|
||||
// replace image with corresponding build image.
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{version_str}"),
|
||||
image: format!("{image_name}:{image_tag}"),
|
||||
};
|
||||
let first_registry = build
|
||||
.config
|
||||
@@ -207,6 +206,12 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let deployment_id = deployment.id.clone();
|
||||
// Track the name the container / service is deployed under,
|
||||
// so the Deployment stays matched to it even if the
|
||||
// name configuration changes before the next deploy.
|
||||
let fresh_name = deployment.custom_name().to_string();
|
||||
let prev_deployed_name = deployment.info.deployed_name.clone();
|
||||
let mut deployed = false;
|
||||
|
||||
match swarm_or_server {
|
||||
SwarmOrServer::None => unreachable!(),
|
||||
@@ -222,7 +227,8 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
.await
|
||||
{
|
||||
Ok(logs) => {
|
||||
update_cache_for_swarm(&swarm, true).await;
|
||||
refresh_swarm_cache(&swarm, true).await;
|
||||
deployed = logs.iter().all(|log| log.success);
|
||||
update.logs.extend(logs)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -246,7 +252,8 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
.await
|
||||
{
|
||||
Ok(log) => {
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
deployed = log.success;
|
||||
update.logs.push(log)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -263,6 +270,11 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
&deployment_id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: Default::default(),
|
||||
deployed_name: if deployed {
|
||||
fresh_name
|
||||
} else {
|
||||
prev_deployed_name
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -274,6 +286,11 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -401,7 +418,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;
|
||||
@@ -418,7 +435,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,
|
||||
@@ -426,7 +443,11 @@ impl Resolve<ExecuteArgs> for PullDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -450,7 +471,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -461,6 +482,10 @@ impl Resolve<ExecuteArgs> for PullDeployment {
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -472,7 +497,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,
|
||||
@@ -480,7 +505,11 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -504,7 +533,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.starting = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -515,7 +544,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::StartContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -527,8 +556,12 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -540,7 +573,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,
|
||||
@@ -548,7 +581,11 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -572,7 +609,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.restarting = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -583,7 +620,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RestartContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -597,8 +634,12 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -610,7 +651,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,
|
||||
@@ -618,7 +659,11 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -642,7 +687,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pausing = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -653,7 +698,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::PauseContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -665,8 +710,12 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -678,7 +727,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,
|
||||
@@ -686,7 +735,11 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -710,7 +763,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.unpausing = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -721,7 +774,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::UnpauseContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -735,8 +788,12 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -748,7 +805,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,
|
||||
@@ -758,7 +815,11 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -782,7 +843,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.stopping = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -793,7 +854,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::StopContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
signal: self
|
||||
.signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
@@ -813,8 +874,12 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -837,18 +902,20 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDestroyDeployment>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
@@ -861,7 +928,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,
|
||||
@@ -871,7 +938,11 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (deployment, swarm_or_server) = setup_deployment_execution(
|
||||
&self.deployment,
|
||||
@@ -890,7 +961,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.destroying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -904,13 +975,13 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
match swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
api::swarm::RemoveSwarmServices {
|
||||
services: vec![deployment.name],
|
||||
services: vec![deployment.deployed_name().to_string()],
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(log) => {
|
||||
update_cache_for_swarm(&swarm, true).await;
|
||||
refresh_swarm_cache(&swarm, true).await;
|
||||
log
|
||||
}
|
||||
Err(e) => Log::error(
|
||||
@@ -925,7 +996,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RemoveContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
signal: self
|
||||
.signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
@@ -938,7 +1009,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(
|
||||
@@ -951,9 +1022,33 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
}
|
||||
};
|
||||
|
||||
// Clear the tracked deployed name once the container / service
|
||||
// is confirmed removed, so the next deploy uses the fresh name.
|
||||
if log.success
|
||||
&& let Err(e) = resource::update_info::<Deployment>(
|
||||
&deployment.id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: deployment
|
||||
.info
|
||||
.latest_image_digest
|
||||
.clone(),
|
||||
deployed_name: Default::default(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Failed to clear deployment {} ({}) deployed name after destroy | {e:#}",
|
||||
deployment.name, deployment.id,
|
||||
);
|
||||
}
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{fmt::Write as _, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::run_komodo_standard_command;
|
||||
use command::{CommandOptions, run_komodo_standard_command};
|
||||
use database::{
|
||||
bson::{Document, doc},
|
||||
mungos::find::find_collect,
|
||||
@@ -15,7 +15,7 @@ use komodo_client::{
|
||||
},
|
||||
entities::{
|
||||
SwarmOrServer, deployment::DeploymentState, server::ServerState,
|
||||
stack::StackState,
|
||||
stack::StackState, swarm::SwarmState,
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCodeError;
|
||||
@@ -39,7 +39,7 @@ use crate::{
|
||||
resource::rotate_server_keys,
|
||||
state::{
|
||||
db_client, deployment_status_cache, server_status_cache,
|
||||
stack_status_cache,
|
||||
stack_status_cache, swarm_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,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(
|
||||
@@ -133,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(
|
||||
@@ -159,8 +167,8 @@ impl Resolve<ExecuteArgs> for BackupCoreDatabase {
|
||||
|
||||
let res = run_komodo_standard_command(
|
||||
"Backup Core Database",
|
||||
None,
|
||||
"km database backup --yes",
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -186,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(
|
||||
@@ -232,6 +244,7 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
|
||||
.context("Failed to query for stacks from database")?;
|
||||
|
||||
let server_status_cache = server_status_cache();
|
||||
let swarm_status_cache = swarm_status_cache();
|
||||
let stack_status_cache = stack_status_cache();
|
||||
|
||||
// Will be edited later at update.logs[0]
|
||||
@@ -243,7 +256,7 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Only pull running stacks.
|
||||
// Only check running stacks.
|
||||
if !matches!(status.curr.state, StackState::Running) {
|
||||
continue;
|
||||
}
|
||||
@@ -255,43 +268,58 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
|
||||
&servers,
|
||||
)?;
|
||||
|
||||
if let SwarmOrServer::None = &swarm_or_server {
|
||||
continue;
|
||||
// Ensure server / swarm is reachable
|
||||
match &swarm_or_server {
|
||||
SwarmOrServer::None => continue,
|
||||
SwarmOrServer::Server(server) => {
|
||||
if !server_status_cache
|
||||
.get(&server.id)
|
||||
.await
|
||||
.map(|s| matches!(s.state, ServerState::Ok))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
SwarmOrServer::Swarm(swarm) => {
|
||||
if !swarm_status_cache
|
||||
.get(&swarm.id)
|
||||
.await
|
||||
.map(|s| {
|
||||
matches!(
|
||||
s.state,
|
||||
SwarmState::Healthy | SwarmState::Unhealthy
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let 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()
|
||||
if let Err(e) = check_stack_for_update_inner(
|
||||
stack.id,
|
||||
&swarm_or_server,
|
||||
self.skip_auto_update,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = check_stack_for_update_inner(
|
||||
stack.id,
|
||||
&swarm_or_server,
|
||||
self.skip_auto_update,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
&format!("Check Stack {}", stack.name),
|
||||
format_serror(&e.into()),
|
||||
);
|
||||
} else {
|
||||
if !update.logs[0].stdout.is_empty() {
|
||||
update.logs[0].stdout.push('\n');
|
||||
}
|
||||
|
||||
update.logs[0].stdout.push_str(&format!(
|
||||
"Checked Stack {} ✅",
|
||||
bold(&stack.name)
|
||||
));
|
||||
update.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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +336,7 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Only pull running deployments.
|
||||
// Only check running deployments.
|
||||
if !matches!(status.curr.state, DeploymentState::Running) {
|
||||
continue;
|
||||
}
|
||||
@@ -320,8 +348,34 @@ impl Resolve<ExecuteArgs> for GlobalAutoUpdate {
|
||||
&servers,
|
||||
)?;
|
||||
|
||||
if let SwarmOrServer::None = &swarm_or_server {
|
||||
continue;
|
||||
// Ensure server / swarm is reachable
|
||||
match &swarm_or_server {
|
||||
SwarmOrServer::None => continue,
|
||||
SwarmOrServer::Server(server) => {
|
||||
if !server_status_cache
|
||||
.get(&server.id)
|
||||
.await
|
||||
.map(|s| matches!(s.state, ServerState::Ok))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
SwarmOrServer::Swarm(swarm) => {
|
||||
if !swarm_status_cache
|
||||
.get(&swarm.id)
|
||||
.await
|
||||
.map(|s| {
|
||||
matches!(
|
||||
s.state,
|
||||
SwarmState::Healthy | SwarmState::Unhealthy
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = deployment.name.clone();
|
||||
@@ -368,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(
|
||||
@@ -482,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,
|
||||
@@ -490,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(
|
||||
|
||||
@@ -13,6 +13,7 @@ use komodo_client::{
|
||||
entities::{
|
||||
Operation,
|
||||
permission::PermissionLevel,
|
||||
resource::ResourceQuery,
|
||||
update::{Log, Update},
|
||||
user::User,
|
||||
},
|
||||
@@ -29,7 +30,10 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::KomodoAuthImpl,
|
||||
helpers::update::{init_execution_update, update_update},
|
||||
helpers::{
|
||||
query::get_all_tags,
|
||||
update::{init_execution_update, update_update},
|
||||
},
|
||||
resource::{KomodoResource, list_full_for_user_using_pattern},
|
||||
state::db_client,
|
||||
};
|
||||
@@ -49,9 +53,9 @@ mod sync;
|
||||
use super::Variant;
|
||||
|
||||
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,
|
||||
}
|
||||
@@ -60,7 +64,7 @@ pub struct ExecuteArgs {
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
|
||||
)]
|
||||
#[strum_discriminants(name(ExecuteRequestVariant), derive(Display))]
|
||||
#[strum_discriminants(name(ExecuteRequestMethod), derive(Display))]
|
||||
#[args(ExecuteArgs)]
|
||||
#[response(JsonString)]
|
||||
#[error(mogh_error::Error)]
|
||||
@@ -111,10 +115,12 @@ pub enum ExecuteRequest {
|
||||
// ==== PROCEDURE ====
|
||||
RunProcedure(RunProcedure),
|
||||
BatchRunProcedure(BatchRunProcedure),
|
||||
CancelProcedure(CancelProcedure),
|
||||
|
||||
// ==== ACTION ====
|
||||
RunAction(RunAction),
|
||||
BatchRunAction(BatchRunAction),
|
||||
CancelAction(CancelAction),
|
||||
|
||||
// ==== SYNC ====
|
||||
RunSync(RunSync),
|
||||
@@ -148,6 +154,7 @@ pub enum ExecuteRequest {
|
||||
|
||||
// ==== SWARM ====
|
||||
RemoveSwarmNodes(RemoveSwarmNodes),
|
||||
UpdateSwarmNode(UpdateSwarmNode),
|
||||
RemoveSwarmStacks(RemoveSwarmStacks),
|
||||
RemoveSwarmServices(RemoveSwarmServices),
|
||||
CreateSwarmConfig(CreateSwarmConfig),
|
||||
@@ -249,11 +256,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,
|
||||
@@ -268,7 +283,7 @@ pub fn inner_handler(
|
||||
find_one_by_id(&db_client().updates, &update_id)
|
||||
.await
|
||||
.context("Failed to query to db")?
|
||||
.context("No update exists with given id")?;
|
||||
.context("No Update exists with given id")?;
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
update_update(update).await
|
||||
@@ -277,7 +292,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:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -293,25 +311,44 @@ async fn task(
|
||||
user: User,
|
||||
update: Update,
|
||||
) -> anyhow::Result<String> {
|
||||
let variant: ExecuteRequestVariant = (&request).into();
|
||||
let method: ExecuteRequestMethod = (&request).into();
|
||||
|
||||
let user_id = user.id.clone();
|
||||
let username = user.username.clone();
|
||||
|
||||
info!(
|
||||
"EXECUTE REQUEST {id} | METHOD: {variant} | USER: {} ({})",
|
||||
user.username, user.id
|
||||
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} | METHOD: {variant} | ERROR: {e:#}");
|
||||
warn!(
|
||||
api = "Execute",
|
||||
task_id = id.to_string(),
|
||||
method = method.to_string(),
|
||||
user_id,
|
||||
username,
|
||||
"EXECUTE REQUEST | ERROR: {e:#}"
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
@@ -325,14 +362,26 @@ trait BatchExecute {
|
||||
#[instrument("BatchExecute", skip(user))]
|
||||
async fn batch_execute<E: BatchExecute>(
|
||||
pattern: &str,
|
||||
tags: Vec<String>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<BatchExecutionResponse> {
|
||||
let all_tags = if tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let resources = list_full_for_user_using_pattern::<E::Resource>(
|
||||
pattern,
|
||||
Default::default(),
|
||||
ResourceQuery {
|
||||
tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&[],
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::bson::to_document,
|
||||
};
|
||||
use formatting::{Color, bold, colored, format_serror, muted};
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
BatchExecutionResponse, BatchRunProcedure, RunProcedure,
|
||||
BatchExecutionResponse, BatchRunProcedure, CancelProcedure,
|
||||
RunProcedure,
|
||||
},
|
||||
entities::{
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
@@ -19,13 +21,14 @@ use komodo_client::{
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
alert::send_alerts,
|
||||
helpers::{procedure::execute_procedure, update::update_update},
|
||||
permission::get_check_permissions,
|
||||
resource::refresh_procedure_state_cache,
|
||||
state::{action_states, db_client},
|
||||
state::{action_states, db_client, procedure_cancel_cache},
|
||||
};
|
||||
|
||||
use super::{ExecuteArgs, ExecuteRequest};
|
||||
@@ -48,8 +51,12 @@ impl Resolve<ExecuteArgs> for BatchRunProcedure {
|
||||
ExecuteArgs { user, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunProcedure>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunProcedure>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -59,7 +66,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,7 +74,11 @@ impl Resolve<ExecuteArgs> for RunProcedure {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
Ok(
|
||||
resolve_inner(self.procedure, user.clone(), update.clone())
|
||||
@@ -113,14 +124,22 @@ fn resolve_inner(
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure procedure not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.running = true)?;
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
procedure_cancel_cache()
|
||||
.insert(procedure.id.clone(), cancel.clone())
|
||||
.await;
|
||||
|
||||
let update = Mutex::new(update);
|
||||
|
||||
let res = execute_procedure(&procedure, &update).await;
|
||||
let res = execute_procedure(&procedure, &update, cancel).await;
|
||||
|
||||
procedure_cancel_cache().remove(&procedure.id).await;
|
||||
|
||||
let mut update = update.into_inner();
|
||||
|
||||
@@ -136,11 +155,15 @@ fn resolve_inner(
|
||||
);
|
||||
}
|
||||
Err(e) => update
|
||||
.push_error_log("execution error", format_serror(&e.into())),
|
||||
.push_error_log("Execution error", format_serror(&e.into())),
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
@@ -180,3 +203,47 @@ fn resolve_inner(
|
||||
Ok(update)
|
||||
})
|
||||
}
|
||||
|
||||
impl Resolve<ExecuteArgs> for CancelProcedure {
|
||||
#[instrument(
|
||||
"CancelProcedure",
|
||||
skip_all,
|
||||
fields(
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
update_id = update.id,
|
||||
procedure = self.procedure,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let procedure = get_check_permissions::<Procedure>(
|
||||
&self.procedure,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
procedure_cancel_cache()
|
||||
.get(&procedure.id)
|
||||
.await
|
||||
.context("Procedure run cancel token not found")?
|
||||
.cancel();
|
||||
|
||||
let mut update = update.clone();
|
||||
update.push_simple_log(
|
||||
"Cancel Triggered",
|
||||
"Procedure cancel has been triggered. The procedure will exit after the currently running stage is complete.",
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchCloneRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchCloneRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +81,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,7 +89,11 @@ impl Resolve<ExecuteArgs> for CloneRepo {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let mut repo = get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
@@ -99,7 +108,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.cloning = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -170,6 +179,10 @@ impl Resolve<ExecuteArgs> for CloneRepo {
|
||||
);
|
||||
};
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
handle_repo_update_return(update).await
|
||||
}
|
||||
}
|
||||
@@ -186,18 +199,23 @@ 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
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchPullRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchPullRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -207,7 +225,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,7 +233,11 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let mut repo = get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
@@ -230,7 +252,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -305,6 +327,10 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
);
|
||||
};
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
handle_repo_update_return(update).await
|
||||
}
|
||||
}
|
||||
@@ -363,18 +389,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchBuildRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchBuildRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -384,7 +415,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,7 +423,11 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let mut repo = get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
@@ -411,7 +446,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -478,7 +513,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
repo.name.clone(),
|
||||
None,
|
||||
builder,
|
||||
&mut update,
|
||||
Some(&mut update),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -489,6 +524,9 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
"get builder",
|
||||
format_serror(&e.context("failed to get builder").into()),
|
||||
));
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
return handle_builder_early_return(
|
||||
update, repo.id, repo.name, false,
|
||||
)
|
||||
@@ -521,6 +559,9 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
return handle_builder_early_return(update, repo.id, repo.name, true).await
|
||||
},
|
||||
};
|
||||
@@ -568,6 +609,10 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
@@ -686,13 +731,12 @@ pub async fn validate_cancel_repo_build(
|
||||
)?;
|
||||
|
||||
match (latest_build, latest_cancel) {
|
||||
(Some(build), Some(cancel)) => {
|
||||
if cancel.start_ts > build.start_ts {
|
||||
return Err(anyhow!(
|
||||
"Repo build has already been cancelled"
|
||||
));
|
||||
}
|
||||
(Some(build), Some(cancel))
|
||||
if cancel.start_ts > build.start_ts =>
|
||||
{
|
||||
return Err(anyhow!("Repo build has already been cancelled"));
|
||||
}
|
||||
|
||||
(None, _) => return Err(anyhow!("No repo build in progress")),
|
||||
_ => {}
|
||||
};
|
||||
@@ -705,7 +749,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,7 +757,11 @@ impl Resolve<ExecuteArgs> for CancelRepoBuild {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let repo = get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
|
||||
@@ -14,7 +14,7 @@ use periphery_client::api;
|
||||
|
||||
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,7 +35,11 @@ impl Resolve<ExecuteArgs> for StartContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -52,8 +56,11 @@ impl Resolve<ExecuteArgs> for StartContainer {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.starting_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.starting_containers += 1,
|
||||
|state| state.starting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -76,9 +83,13 @@ impl Resolve<ExecuteArgs> for StartContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -90,7 +101,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,7 +110,11 @@ impl Resolve<ExecuteArgs> for RestartContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -116,8 +131,11 @@ impl Resolve<ExecuteArgs> for RestartContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.restarting_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.restarting_containers += 1,
|
||||
|state| state.restarting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -142,9 +160,13 @@ impl Resolve<ExecuteArgs> for RestartContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -156,7 +178,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,7 +187,11 @@ impl Resolve<ExecuteArgs> for PauseContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -182,8 +208,11 @@ impl Resolve<ExecuteArgs> for PauseContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pausing_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.pausing_containers += 1,
|
||||
|state| state.pausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -206,9 +235,13 @@ impl Resolve<ExecuteArgs> for PauseContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -220,7 +253,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,7 +262,11 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -246,8 +283,11 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.unpausing_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.unpausing_containers += 1,
|
||||
|state| state.unpausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -272,9 +312,13 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -286,7 +330,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,7 +341,11 @@ impl Resolve<ExecuteArgs> for StopContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -314,8 +362,11 @@ impl Resolve<ExecuteArgs> for StopContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.stopping_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.stopping_containers += 1,
|
||||
|state| state.stopping_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -340,9 +391,13 @@ impl Resolve<ExecuteArgs> for StopContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -354,7 +409,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,7 +420,11 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let DestroyContainer {
|
||||
server,
|
||||
@@ -388,8 +447,11 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pruning_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.destroying_containers += 1,
|
||||
|state| state.destroying_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -416,9 +478,13 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -430,7 +496,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,7 +504,11 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -455,8 +525,11 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.starting_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.starting_containers += 1,
|
||||
|state| state.starting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -477,8 +550,12 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
|
||||
);
|
||||
}
|
||||
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -490,7 +567,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,7 +575,11 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -515,8 +596,11 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.restarting_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.restarting_containers += 1,
|
||||
|state| state.restarting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -539,8 +623,12 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
|
||||
);
|
||||
}
|
||||
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -552,7 +640,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,7 +648,11 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -577,8 +669,11 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pausing_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.pausing_containers += 1,
|
||||
|state| state.pausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -599,8 +694,12 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
|
||||
);
|
||||
}
|
||||
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -612,7 +711,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,7 +719,11 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -637,8 +740,11 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.unpausing_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.unpausing_containers += 1,
|
||||
|state| state.unpausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -661,8 +767,12 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
|
||||
);
|
||||
}
|
||||
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -674,7 +784,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,7 +792,11 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -699,8 +813,11 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.stopping_containers = true)?;
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.stopping_containers += 1,
|
||||
|state| state.stopping_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -721,8 +838,12 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
|
||||
);
|
||||
}
|
||||
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -734,7 +855,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,7 +863,11 @@ impl Resolve<ExecuteArgs> for PruneContainers {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -759,7 +884,7 @@ impl Resolve<ExecuteArgs> for PruneContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -785,9 +910,13 @@ impl Resolve<ExecuteArgs> for PruneContainers {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -799,7 +928,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,7 +937,11 @@ impl Resolve<ExecuteArgs> for DeleteNetwork {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -846,7 +979,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 +993,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,7 +1001,11 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -885,7 +1022,7 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_networks = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -909,9 +1046,13 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -923,7 +1064,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,7 +1073,11 @@ impl Resolve<ExecuteArgs> for DeleteImage {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -967,7 +1112,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 +1126,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,7 +1134,11 @@ impl Resolve<ExecuteArgs> for PruneImages {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1006,7 +1155,7 @@ impl Resolve<ExecuteArgs> for PruneImages {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_images = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1028,9 +1177,13 @@ impl Resolve<ExecuteArgs> for PruneImages {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1042,7 +1195,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,7 +1204,11 @@ impl Resolve<ExecuteArgs> for DeleteVolume {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1089,7 +1246,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 +1260,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,7 +1268,11 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1128,7 +1289,7 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_volumes = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1150,9 +1311,13 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1164,7 +1329,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,7 +1337,11 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1189,7 +1358,7 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_builders = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1211,9 +1380,13 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1225,7 +1398,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,7 +1406,11 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1250,7 +1427,7 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_buildx = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1272,9 +1449,13 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1286,7 +1467,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,7 +1475,11 @@ impl Resolve<ExecuteArgs> for PruneSystem {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
@@ -1311,7 +1496,7 @@ impl Resolve<ExecuteArgs> for PruneSystem {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pruning_system = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1332,9 +1517,13 @@ impl Resolve<ExecuteArgs> for PruneSystem {
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update_cache_for_server(&server, true).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -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,18 +69,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeployStack>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchDeployStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +95,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,7 +105,11 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (mut stack, swarm_or_server) = setup_stack_execution(
|
||||
&self.stack,
|
||||
@@ -127,7 +136,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
|
||||
// Will check to ensure stack not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -313,14 +322,18 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -342,18 +355,20 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeployStackIfChanged>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
@@ -366,7 +381,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,
|
||||
@@ -375,7 +390,11 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let stack = get_check_permissions::<Stack>(
|
||||
&self.stack,
|
||||
@@ -433,7 +452,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
.resolve(&ExecuteArgs {
|
||||
user: user.clone(),
|
||||
update,
|
||||
id: *id,
|
||||
task_id: *task_id,
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -572,7 +591,7 @@ async fn deploy_services(
|
||||
.resolve(&ExecuteArgs {
|
||||
user: user.clone(),
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -603,7 +622,7 @@ async fn restart_services(
|
||||
.resolve(&ExecuteArgs {
|
||||
user: user.clone(),
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -766,18 +785,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchPullStack>(&self.pattern, user)
|
||||
.await?,
|
||||
super::batch_execute::<BatchPullStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -878,7 +902,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)
|
||||
}
|
||||
@@ -888,7 +912,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,
|
||||
@@ -897,7 +921,11 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (stack, swarm_or_server) = setup_stack_execution(
|
||||
&self.stack,
|
||||
@@ -931,7 +959,7 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
|
||||
// Will check to ensure stack not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -948,6 +976,10 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
|
||||
update.logs.extend(res.logs);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -959,7 +991,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,
|
||||
@@ -968,7 +1000,11 @@ impl Resolve<ExecuteArgs> for StartStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
execute_compose::<StartStack>(
|
||||
&self.stack,
|
||||
@@ -988,7 +1024,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,
|
||||
@@ -997,7 +1033,11 @@ impl Resolve<ExecuteArgs> for RestartStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
execute_compose::<RestartStack>(
|
||||
&self.stack,
|
||||
@@ -1019,7 +1059,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,
|
||||
@@ -1028,7 +1068,11 @@ impl Resolve<ExecuteArgs> for PauseStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
execute_compose::<PauseStack>(
|
||||
&self.stack,
|
||||
@@ -1048,7 +1092,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,
|
||||
@@ -1057,7 +1101,11 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
execute_compose::<UnpauseStack>(
|
||||
&self.stack,
|
||||
@@ -1077,7 +1125,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,
|
||||
@@ -1086,7 +1134,11 @@ impl Resolve<ExecuteArgs> for StopStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
execute_compose::<StopStack>(
|
||||
&self.stack,
|
||||
@@ -1118,18 +1170,23 @@ 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,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, id, .. }: &ExecuteArgs,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
super::batch_execute::<BatchDestroyStack>(&self.pattern, user)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
super::batch_execute::<BatchDestroyStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1138,7 +1195,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,
|
||||
@@ -1149,7 +1206,11 @@ impl Resolve<ExecuteArgs> for DestroyStack {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (stack, swarm_or_server) = setup_stack_execution(
|
||||
&self.stack,
|
||||
@@ -1178,7 +1239,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
|
||||
|
||||
// Will check to ensure stack not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.destroying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1205,9 +1266,13 @@ 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();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1233,7 +1298,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,
|
||||
@@ -1243,7 +1308,11 @@ impl Resolve<ExecuteArgs> for RunStackService {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let (mut stack, swarm_or_server) = setup_stack_execution(
|
||||
&self.stack,
|
||||
@@ -1274,7 +1343,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
|
||||
let action_state =
|
||||
action_states().stack.get_or_insert_default(&stack.id).await;
|
||||
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1334,6 +1403,10 @@ impl Resolve<ExecuteArgs> for RunStackService {
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
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 mogh_resolver::Resolve;
|
||||
@@ -12,7 +8,7 @@ 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",
|
||||
|
||||
+106
-198
@@ -21,6 +21,7 @@ use komodo_client::{
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
update::{Log, Update},
|
||||
user::sync_user,
|
||||
@@ -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,7 +64,11 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, update, id }: &ExecuteArgs,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> mogh_error::Result<Update> {
|
||||
let RunSync {
|
||||
sync,
|
||||
@@ -93,7 +98,7 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure sync not already busy before updating.
|
||||
let _action_guard =
|
||||
let action_guard =
|
||||
action_state.update(|state| state.syncing = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -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,
|
||||
@@ -395,9 +283,11 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// New resource types need to be added here manually.
|
||||
if deploy_cache.is_empty()
|
||||
&& resource_sync_deltas.no_changes()
|
||||
&& server_deltas.no_changes()
|
||||
&& swarm_deltas.no_changes()
|
||||
&& deployment_deltas.no_changes()
|
||||
&& stack_deltas.no_changes()
|
||||
&& build_deltas.no_changes()
|
||||
@@ -421,11 +311,19 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
),
|
||||
);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
// =================
|
||||
// =====================================================
|
||||
// The ordering these are executed does matter, since
|
||||
// latter resources may depend on prior synced resources
|
||||
// already being updated with the declared state.
|
||||
// =====================================================
|
||||
|
||||
// No deps
|
||||
maybe_extend(
|
||||
@@ -446,10 +344,7 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
ResourceSync::execute_sync_updates(resource_sync_deltas).await,
|
||||
);
|
||||
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Server::execute_sync_updates(server_deltas).await,
|
||||
@@ -463,34 +358,43 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
Action::execute_sync_updates(action_deltas).await,
|
||||
);
|
||||
|
||||
// Dependent on server
|
||||
// Depends on server
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Swarm::execute_sync_updates(swarm_deltas).await,
|
||||
);
|
||||
// Depends on server
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Builder::execute_sync_updates(builder_deltas).await,
|
||||
);
|
||||
// Depends on server / builder
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Repo::execute_sync_updates(repo_deltas).await,
|
||||
);
|
||||
|
||||
// Dependant on builder
|
||||
// Depends on builder / repo
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Build::execute_sync_updates(build_deltas).await,
|
||||
);
|
||||
|
||||
// Dependant on server / build
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Deployment::execute_sync_updates(deployment_deltas).await,
|
||||
);
|
||||
// stack only depends on server, but maybe will depend on build later.
|
||||
// Depends on server / repo
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Stack::execute_sync_updates(stack_deltas).await,
|
||||
);
|
||||
|
||||
// Dependant on everything
|
||||
// Depends on repo
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
ResourceSync::execute_sync_updates(resource_sync_deltas).await,
|
||||
);
|
||||
// Depends on server / build
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Deployment::execute_sync_updates(deployment_deltas).await,
|
||||
);
|
||||
// Depends on everything
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Procedure::execute_sync_updates(procedure_deltas).await,
|
||||
@@ -542,6 +446,10 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use axum::http::HeaderMap;
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit as _, Mac};
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::{Router, http::HeaderMap};
|
||||
use komodo_client::entities::resource::Resource;
|
||||
use mogh_cache::CloneCache;
|
||||
@@ -41,12 +40,21 @@ trait VerifySecret {
|
||||
/// Implemented on the integration struct, eg [integrations::github::Github]
|
||||
trait ExtractBranch {
|
||||
fn extract_branch(body: &str) -> anyhow::Result<String>;
|
||||
fn verify_branch(body: &str, expected: &str) -> anyhow::Result<()> {
|
||||
/// Whether the webhook body's branch matches `expected`.
|
||||
/// A mismatch is routine and only logged at debug; errors
|
||||
/// only when the branch cannot be extracted from the body.
|
||||
fn branch_matches(
|
||||
body: &str,
|
||||
expected: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let branch = Self::extract_branch(body)?;
|
||||
if branch == expected {
|
||||
Ok(())
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!("request branch does not match expected"))
|
||||
debug!(
|
||||
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{expected}'"
|
||||
);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{str::FromStr, sync::OnceLock};
|
||||
use std::{str::FromStr, sync::OnceLock, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use komodo_client::{
|
||||
@@ -23,6 +23,7 @@ use crate::{
|
||||
},
|
||||
helpers::update::init_execution_update,
|
||||
resource,
|
||||
state::action_states,
|
||||
};
|
||||
|
||||
use super::{ANY_BRANCH, ListenerLockCache};
|
||||
@@ -37,11 +38,6 @@ impl super::CustomSecret for Build {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_locks() -> &'static ListenerLockCache {
|
||||
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
build: Build,
|
||||
body: String,
|
||||
@@ -50,12 +46,6 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = build_locks().get_or_insert_default(&build.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
// Use the correct target branch when using linked repo.
|
||||
let branch = if build.config.linked_repo.is_empty() {
|
||||
build.config.branch
|
||||
@@ -67,25 +57,82 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
.branch
|
||||
};
|
||||
|
||||
B::verify_branch(&body, &branch)?;
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Cancel if currently building
|
||||
if action_states()
|
||||
.build
|
||||
.get(&build.id)
|
||||
.await
|
||||
.and_then(|states| {
|
||||
states.get().ok().map(|states| states.building)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
let user = git_webhook_user().to_owned();
|
||||
let cancel = ExecuteRequest::CancelBuild(CancelBuild {
|
||||
build: build.id.clone(),
|
||||
});
|
||||
let update = init_execution_update(&cancel, &user).await?;
|
||||
let ExecuteRequest::CancelBuild(cancel) = cancel else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
cancel
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
|
||||
poll_build_until_cancelled(&build.id).await?;
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req = ExecuteRequest::RunBuild(RunBuild { build: build.id });
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunBuild(req) = req else {
|
||||
let run = ExecuteRequest::RunBuild(RunBuild { build: build.id });
|
||||
let update = init_execution_update(&run, &user).await?;
|
||||
let ExecuteRequest::RunBuild(run) = run else {
|
||||
unreachable!()
|
||||
};
|
||||
req
|
||||
|
||||
run
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn poll_build_until_cancelled(
|
||||
build_id: &String,
|
||||
) -> anyhow::Result<()> {
|
||||
let action_states = action_states();
|
||||
// Poll to ensure cancelled
|
||||
for _ in 0..10 {
|
||||
if !action_states
|
||||
.build
|
||||
.get(build_id)
|
||||
.await
|
||||
.and_then(|states| {
|
||||
states.get().ok().map(|states| states.building)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
Err(anyhow!("Build still running after cancel"))
|
||||
}
|
||||
|
||||
// ======
|
||||
// REPO
|
||||
// ======
|
||||
@@ -121,7 +168,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 +192,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 +216,7 @@ impl RepoExecution for BuildRepo {
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.error)?;
|
||||
@@ -220,7 +267,9 @@ async fn handle_repo_webhook_inner<
|
||||
let lock = repo_locks().get_or_insert_default(&repo.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
B::verify_branch(&body, &repo.config.branch)?;
|
||||
if !B::branch_matches(&body, &repo.config.branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
E::resolve(repo).await
|
||||
}
|
||||
@@ -272,7 +321,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 +339,7 @@ impl StackExecution for DeployStack {
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.error)?;
|
||||
@@ -351,7 +400,9 @@ pub async fn handle_stack_webhook_inner<
|
||||
.branch
|
||||
};
|
||||
|
||||
B::verify_branch(&body, &branch)?;
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
E::resolve(stack).await.map_err(|e| e.error)
|
||||
}
|
||||
@@ -403,7 +454,7 @@ impl SyncExecution for RunSync {
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
id: Uuid::new_v4(),
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.error)?;
|
||||
@@ -464,7 +515,9 @@ async fn handle_sync_webhook_inner<
|
||||
.branch
|
||||
};
|
||||
|
||||
B::verify_branch(&body, &branch)?;
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
E::resolve(sync).await
|
||||
}
|
||||
@@ -501,8 +554,10 @@ pub async fn handle_procedure_webhook<B: super::ExtractBranch>(
|
||||
procedure_locks().get_or_insert_default(&procedure.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
if target_branch != ANY_BRANCH {
|
||||
B::verify_branch(&body, target_branch)?;
|
||||
if target_branch != ANY_BRANCH
|
||||
&& !B::branch_matches(&body, target_branch)?
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
@@ -517,7 +572,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)?;
|
||||
@@ -557,7 +612,10 @@ pub async fn handle_action_webhook<B: super::ExtractBranch>(
|
||||
let branch = B::extract_branch(&body)?;
|
||||
|
||||
if target_branch != ANY_BRANCH && branch != target_branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
debug!(
|
||||
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{target_branch}'"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
@@ -583,7 +641,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)?;
|
||||
|
||||
@@ -222,8 +222,31 @@ where
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
P::verify_secret(headers, body, R::custom_secret(&resource))
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
info!(
|
||||
resource_type = R::resource_type().to_string(),
|
||||
resource_id = id,
|
||||
source_ip = ip.to_string(),
|
||||
"Successfully authenticated incoming webhook"
|
||||
);
|
||||
|
||||
debug!(
|
||||
resource_type = R::resource_type().to_string(),
|
||||
resource_id = id,
|
||||
source_ip = ip.to_string(),
|
||||
"Webhook body: {body}"
|
||||
);
|
||||
|
||||
mogh_error::Result::Ok(resource)
|
||||
}
|
||||
.with_failure_rate_limit_using_ip(&GENERAL_RATE_LIMITER, &ip)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
resource_id = id,
|
||||
source_ip = ip.to_string(),
|
||||
"Incoming webhook failed | ERROR: {:#}",
|
||||
e.error
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
action::{
|
||||
Action, ActionActionState, ActionListItem, ActionState,
|
||||
Action, ActionActionState, ActionListItem, ActionSortBy,
|
||||
ActionState,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
@@ -11,13 +12,13 @@ use komodo_client::{
|
||||
use mogh_resolver::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_all_tags,
|
||||
helpers::query::{get_action_state, get_all_tags},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
state::{action_state_cache, action_states},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetAction {
|
||||
async fn resolve(
|
||||
@@ -45,15 +46,45 @@ impl Resolve<ReadArgs> for ListActions {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Action>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ActionListItem> =
|
||||
match self.sort_by {
|
||||
ActionSortBy::Name => resource::ListItemSort::Name,
|
||||
ActionSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ActionSortBy::NextRun => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.next_scheduled_run
|
||||
.cmp(&b.info.next_scheduled_run)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let actions = resource::list_items_for_user::<Action>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|action| {
|
||||
states.is_empty() || states.contains(&action.info.state)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(actions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +98,28 @@ impl Resolve<ReadArgs> for ListFullActions {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Action>(
|
||||
resource::list_full_for_user_filtered::<Action, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|action| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states.contains(&get_action_state(&action.id).await)
|
||||
{
|
||||
Some(action)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -107,6 +154,8 @@ impl Resolve<ReadArgs> for GetActionsSummary {
|
||||
) -> mogh_error::Result<GetActionsSummaryResponse> {
|
||||
let actions = resource::list_full_for_user::<Action>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Resolve<ReadArgs> for ListAlerts {
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "ts": -1 })
|
||||
.limit(NUM_ALERTS_PER_PAGE as i64)
|
||||
.skip(self.page * NUM_ALERTS_PER_PAGE)
|
||||
.skip(self.page.saturating_mul(NUM_ALERTS_PER_PAGE))
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
alerter::{Alerter, AlerterListItem},
|
||||
alerter::{Alerter, AlerterListItem, AlerterSortBy},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
};
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetAlerter {
|
||||
async fn resolve(
|
||||
@@ -45,12 +45,30 @@ impl Resolve<ReadArgs> for ListAlerters {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<AlerterListItem> =
|
||||
match self.sort_by {
|
||||
AlerterSortBy::Name => resource::ListItemSort::Name,
|
||||
AlerterSortBy::Type => {
|
||||
resource::ListItemSort::DbField("config.endpoint.type")
|
||||
}
|
||||
AlerterSortBy::Enabled => {
|
||||
resource::ListItemSort::DbField("config.enabled")
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Alerter>(
|
||||
resource::list_items_for_user::<Alerter>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -67,9 +85,12 @@ impl Resolve<ReadArgs> for ListFullAlerters {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Alerter>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -85,6 +106,8 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetAlertersSummaryResponse> {
|
||||
let query = match list_resource_ids_for_user::<Alerter>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -11,7 +11,9 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
Operation,
|
||||
build::{Build, BuildActionState, BuildListItem, BuildState},
|
||||
build::{
|
||||
Build, BuildActionState, BuildListItem, BuildSortBy, BuildState,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
update::UpdateStatus,
|
||||
},
|
||||
@@ -25,7 +27,7 @@ use crate::{
|
||||
state::{action_states, build_state_cache, db_client},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetBuild {
|
||||
async fn resolve(
|
||||
@@ -53,15 +55,47 @@ impl Resolve<ReadArgs> for ListBuilds {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Build>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<BuildListItem> =
|
||||
match self.sort_by {
|
||||
BuildSortBy::Name => resource::ListItemSort::Name,
|
||||
BuildSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
BuildSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let builds = resource::list_items_for_user::<Build>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|build| states.is_empty() || states.contains(&build.info.state),
|
||||
)
|
||||
.await?;
|
||||
Ok(builds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +109,29 @@ impl Resolve<ReadArgs> for ListFullBuilds {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Build>(
|
||||
resource::list_full_for_user_filtered::<Build, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|build| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&resource::get_build_state(&build.id).await)
|
||||
{
|
||||
Some(build)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -115,6 +166,8 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
|
||||
) -> mogh_error::Result<GetBuildsSummaryResponse> {
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -164,7 +217,8 @@ impl Resolve<ReadArgs> for GetBuildMonthlyStats {
|
||||
let curr_ts = unix_timestamp_ms() as i64;
|
||||
let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS;
|
||||
|
||||
let close_ts = next_day - self.page as i64 * 30 * ONE_DAY_MS;
|
||||
let close_ts =
|
||||
next_day - (self.page as i64).saturating_mul(30 * ONE_DAY_MS);
|
||||
let open_ts = close_ts - 30 * ONE_DAY_MS;
|
||||
|
||||
let mut build_updates = db_client()
|
||||
@@ -281,6 +335,8 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
|
||||
};
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|
||||
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
builder::{Builder, BuilderListItem},
|
||||
builder::{Builder, BuilderListItem, BuilderSortBy},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
};
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetBuilder {
|
||||
async fn resolve(
|
||||
@@ -45,12 +45,35 @@ impl Resolve<ReadArgs> for ListBuilders {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<BuilderListItem> =
|
||||
match self.sort_by {
|
||||
BuilderSortBy::Name => resource::ListItemSort::Name,
|
||||
BuilderSortBy::Provider => {
|
||||
resource::ListItemSort::DbField("config.type")
|
||||
}
|
||||
BuilderSortBy::InstanceType => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.instance_type
|
||||
.cmp(&b.info.instance_type)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Builder>(
|
||||
resource::list_items_for_user::<Builder>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -67,9 +90,12 @@ impl Resolve<ReadArgs> for ListFullBuilders {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Builder>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -85,6 +111,8 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetBuildersSummaryResponse> {
|
||||
let query = match list_resource_ids_for_user::<Builder>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -6,8 +6,8 @@ use komodo_client::{
|
||||
entities::{
|
||||
SwarmOrServer,
|
||||
deployment::{
|
||||
Deployment, DeploymentActionState, DeploymentConfig,
|
||||
DeploymentListItem, DeploymentState,
|
||||
Deployment, DeploymentActionState, DeploymentListItem,
|
||||
DeploymentSortBy, DeploymentState,
|
||||
},
|
||||
docker::{
|
||||
container::{Container, ContainerStats},
|
||||
@@ -25,7 +25,9 @@ use reqwest::StatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client, query::get_all_tags, swarm::swarm_request,
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_deployment_state},
|
||||
swarm::swarm_request,
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource::{self, setup_deployment_execution},
|
||||
@@ -34,7 +36,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetDeployment {
|
||||
async fn resolve(
|
||||
@@ -63,21 +65,66 @@ impl Resolve<ReadArgs> for ListDeployments {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let deployments = resource::list_for_user::<Deployment>(
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<DeploymentListItem> =
|
||||
match self.sort_by {
|
||||
DeploymentSortBy::Name => resource::ListItemSort::Name,
|
||||
DeploymentSortBy::Image => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.image
|
||||
.cmp(&b.info.image)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
DeploymentSortBy::Host => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
let host_a = if a.info.swarm_id.is_empty() {
|
||||
&a.info.server_name
|
||||
} else {
|
||||
&a.info.swarm_name
|
||||
};
|
||||
let host_b = if b.info.swarm_id.is_empty() {
|
||||
&b.info.server_name
|
||||
} else {
|
||||
&b.info.swarm_name
|
||||
};
|
||||
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
DeploymentSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| {
|
||||
// Use ! with update available to order 'true' first
|
||||
(!a.info.update_available)
|
||||
.cmp(&!b.info.update_available)
|
||||
})
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let deployments = resource::list_items_for_user::<Deployment>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|deployment| {
|
||||
(!only_update_available || deployment.info.update_available)
|
||||
&& (states.is_empty()
|
||||
|| states.contains(&deployment.info.state))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let deployments = if only_update_available {
|
||||
deployments
|
||||
.into_iter()
|
||||
.filter(|deployment| deployment.info.update_available)
|
||||
.collect()
|
||||
} else {
|
||||
deployments
|
||||
};
|
||||
Ok(deployments)
|
||||
}
|
||||
}
|
||||
@@ -92,12 +139,32 @@ impl Resolve<ReadArgs> for ListFullDeployments {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
resource::list_full_for_user_filtered::<Deployment, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|deployment| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states.contains(
|
||||
&get_deployment_state(&deployment.id)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
{
|
||||
Some(deployment)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -154,7 +221,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
|
||||
SwarmOrServer::Swarm(swarm) => swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLog {
|
||||
service: deployment.name,
|
||||
service: deployment.deployed_name().to_string(),
|
||||
tail,
|
||||
timestamps,
|
||||
no_task_ids: false,
|
||||
@@ -167,7 +234,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
|
||||
SwarmOrServer::Server(server) => periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerLog {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
@@ -206,7 +273,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
|
||||
SwarmOrServer::Swarm(swarm) => swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLogSearch {
|
||||
service: deployment.name,
|
||||
service: deployment.deployed_name().to_string(),
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -221,7 +288,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
|
||||
SwarmOrServer::Server(server) => periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerLogSearch {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -274,7 +341,7 @@ impl Resolve<ReadArgs> for InspectDeploymentContainer {
|
||||
periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectContainer {
|
||||
name: deployment.name,
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
.context("Failed to inspect container on server")
|
||||
@@ -307,7 +374,7 @@ impl Resolve<ReadArgs> for InspectDeploymentSwarmService {
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmService {
|
||||
service: deployment.name,
|
||||
service: deployment.deployed_name().to_string(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -321,25 +388,24 @@ impl Resolve<ReadArgs> for GetDeploymentStats {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ContainerStats> {
|
||||
let Deployment {
|
||||
name,
|
||||
config: DeploymentConfig { server_id, .. },
|
||||
..
|
||||
} = get_check_permissions::<Deployment>(
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
&self.deployment,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
if server_id.is_empty() {
|
||||
if deployment.config.server_id.is_empty() {
|
||||
return Err(
|
||||
anyhow!("deployment has no server attached").into(),
|
||||
anyhow!("Deployment has no Server attached").into(),
|
||||
);
|
||||
}
|
||||
let server = resource::get::<Server>(&server_id).await?;
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerStats { name })
|
||||
.request(api::container::GetContainerStats {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.await
|
||||
.context("failed to get stats from periphery")?;
|
||||
Ok(res)
|
||||
@@ -374,6 +440,8 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
|
||||
) -> mogh_error::Result<GetDeploymentsSummaryResponse> {
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -422,6 +490,8 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
|
||||
};
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|
||||
@@ -0,0 +1,590 @@
|
||||
use std::cmp;
|
||||
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use database::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
deployment::Deployment,
|
||||
docker::{
|
||||
container::{
|
||||
Container, ContainerListItem, ContainerSortBy,
|
||||
ContainerStateStatusEnum,
|
||||
},
|
||||
image::{Image, ImageHistoryResponseItem},
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
server::{Server, ServerQuery, ServerState},
|
||||
stack::{Stack, StackServiceNames},
|
||||
update::Log,
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api as periphery;
|
||||
|
||||
use crate::{
|
||||
api::read::{ReadArgs, list_limit},
|
||||
helpers::{periphery_client, query::get_all_tags},
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
resource,
|
||||
stack::compose_container_match_regex,
|
||||
state::{db_client, server_status_cache},
|
||||
};
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainersSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetContainersSummaryResponse> {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get servers from db")?;
|
||||
|
||||
let mut res = GetContainersSummaryResponse::default();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(docker) = &cache.docker {
|
||||
for container in &docker.containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
| ContainerStateStatusEnum::Paused
|
||||
| ContainerStateStatusEnum::Exited => res.stopped += 1,
|
||||
ContainerStateStatusEnum::Running => res.running += 1,
|
||||
ContainerStateStatusEnum::Empty => res.unknown += 1,
|
||||
_ => res.unhealthy += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListAllContainersResponse> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
ServerQuery::builder()
|
||||
.names(self.servers.clone())
|
||||
.tags(self.tags)
|
||||
.build(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut containers = Vec::<ContainerListItem>::new();
|
||||
let limit = list_limit(self.limit);
|
||||
// Match terms case insensitively.
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
let Some(docker) = &cache.docker else {
|
||||
continue;
|
||||
};
|
||||
containers.extend(
|
||||
docker
|
||||
.containers
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
// Apply state filter if defined.
|
||||
(self.state.is_empty() || self.state.contains(&container.state)) &&
|
||||
// Apply terms filter if defined
|
||||
(terms.is_empty()
|
||||
// Match when all terms contained within a name.
|
||||
|| {
|
||||
let name = container.name.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
})
|
||||
})
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
|
||||
// The containers all come from the in memory status cache,
|
||||
// so all matching containers are collected and sorted
|
||||
// before applying pagination.
|
||||
let compare = |a: &ContainerListItem, b: &ContainerListItem| {
|
||||
match self.sort_by {
|
||||
ContainerSortBy::Name => a.name.cmp(&b.name),
|
||||
ContainerSortBy::Server => a.server_name.cmp(&b.server_name),
|
||||
ContainerSortBy::State => a.state.cmp(&b.state),
|
||||
ContainerSortBy::Image => a.image.cmp(&b.image),
|
||||
ContainerSortBy::Networks => {
|
||||
a.networks.first().cmp(&b.networks.first())
|
||||
}
|
||||
ContainerSortBy::Ports => a
|
||||
.ports
|
||||
.first()
|
||||
.map(|port| port.private_port)
|
||||
.cmp(&b.ports.first().map(|port| port.private_port)),
|
||||
ContainerSortBy::Volumes => {
|
||||
a.volumes.first().cmp(&b.volumes.first())
|
||||
}
|
||||
}
|
||||
// Fall back to name based sorting for equal sort keys.
|
||||
// Inside `compare`, so descending sorts are fully descending,
|
||||
// matching the List<Resource> apis.
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
};
|
||||
if self.sort_desc {
|
||||
containers.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
containers.sort_by(|a, b| compare(a, b));
|
||||
}
|
||||
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(containers.into_iter().skip(skip).take(take).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListContainersResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.containers.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Container> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.inspect(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect container: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::InspectContainer {
|
||||
name: self.container,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// First check deployments with a matching deployed / custom container name.
|
||||
// The empty check is required to avoid matching
|
||||
// deployments with no custom name configured.
|
||||
if !self.container.is_empty()
|
||||
&& let Ok(Some(deployment)) = db_client()
|
||||
.deployments
|
||||
.find_one(doc! {
|
||||
"$or": [
|
||||
{ "info.deployed_name": &self.container },
|
||||
{ "config.custom_name": &self.container },
|
||||
]
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// Then check deployments matching by name
|
||||
if let Ok(deployment) =
|
||||
resource::get::<Deployment>(&self.container).await
|
||||
&& deployment.custom_name() == self.container
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// then check stacks
|
||||
let stacks = list_resources_for_user::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check matching stack
|
||||
for stack in stacks {
|
||||
for StackServiceNames {
|
||||
service_name,
|
||||
container_name,
|
||||
..
|
||||
} in stack
|
||||
.info
|
||||
.deployed_services
|
||||
.unwrap_or(stack.info.latest_services)
|
||||
{
|
||||
let is_match = match compose_container_match_regex(&container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
continue;
|
||||
}
|
||||
}.is_match(&self.container);
|
||||
|
||||
if is_match {
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Stack(stack.id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetResourceMatchingContainerResponse { resource: None })
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LOG_LENGTH: u64 = 5000;
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let GetContainerLog {
|
||||
server,
|
||||
container,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLog {
|
||||
name: container,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let SearchContainerLog {
|
||||
server,
|
||||
container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLogSearch {
|
||||
name: container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListComposeProjectsResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.projects.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListNetworks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListNetworksResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.networks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectNetwork {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Network> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect network: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectNetwork {
|
||||
name: self.network,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListImages {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListImagesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.images.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectImage {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Image> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect image: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectImage { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListImageHistory {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot get image history: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::ImageHistory { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListVolumes {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListVolumesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.volumes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectVolume {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Volume> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectVolume { name: self.volume })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use komodo_client::{
|
||||
ResourceTarget,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
config::{DockerRegistry, GitProvider},
|
||||
config::{GitProvider, ImageRegistry},
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
@@ -44,6 +44,7 @@ mod alerter;
|
||||
mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod docker;
|
||||
mod onboarding_key;
|
||||
mod permission;
|
||||
mod procedure;
|
||||
@@ -66,11 +67,17 @@ pub struct ReadArgs {
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
/// Resolve the page limit for List apis, falling back to the
|
||||
/// configured `default_pagination_limit` when not provided.
|
||||
fn list_limit(limit: Option<u64>) -> u64 {
|
||||
limit.unwrap_or(core_config().default_pagination_limit)
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
|
||||
)]
|
||||
#[strum_discriminants(name(ReadRequestVariant), derive(Display))]
|
||||
#[strum_discriminants(name(ReadRequestMethod), derive(Display))]
|
||||
#[args(ReadArgs)]
|
||||
#[response(Response)]
|
||||
#[error(mogh_error::Error)]
|
||||
@@ -80,7 +87,8 @@ enum ReadRequest {
|
||||
GetCoreInfo(GetCoreInfo),
|
||||
ListSecrets(ListSecrets),
|
||||
ListGitProvidersFromConfig(ListGitProvidersFromConfig),
|
||||
ListDockerRegistriesFromConfig(ListDockerRegistriesFromConfig),
|
||||
#[serde(alias = "ListDockerRegistriesFromConfig")]
|
||||
ListImageRegistriesFromConfig(ListImageRegistriesFromConfig),
|
||||
|
||||
// ==== SWARM ====
|
||||
GetSwarmsSummary(GetSwarmsSummary),
|
||||
@@ -117,22 +125,33 @@ enum ReadRequest {
|
||||
// ==== TERMINAL ====
|
||||
ListTerminals(ListTerminals),
|
||||
|
||||
// ==== DOCKER ====
|
||||
GetDockerContainersSummary(GetDockerContainersSummary),
|
||||
ListAllDockerContainers(ListAllDockerContainers),
|
||||
ListDockerContainers(ListDockerContainers),
|
||||
InspectDockerContainer(InspectDockerContainer),
|
||||
// ==== CONTAINER ====
|
||||
#[serde(alias = "GetDockerContainersSummary")]
|
||||
GetContainersSummary(GetContainersSummary),
|
||||
#[serde(alias = "ListAllDockerContainers")]
|
||||
ListAllContainers(ListAllContainers),
|
||||
#[serde(alias = "ListDockerContainers")]
|
||||
ListContainers(ListContainers),
|
||||
#[serde(alias = "InspectDockerContainer")]
|
||||
InspectContainer(InspectContainer),
|
||||
GetResourceMatchingContainer(GetResourceMatchingContainer),
|
||||
GetContainerLog(GetContainerLog),
|
||||
SearchContainerLog(SearchContainerLog),
|
||||
ListComposeProjects(ListComposeProjects),
|
||||
ListDockerNetworks(ListDockerNetworks),
|
||||
InspectDockerNetwork(InspectDockerNetwork),
|
||||
ListDockerImages(ListDockerImages),
|
||||
InspectDockerImage(InspectDockerImage),
|
||||
ListDockerImageHistory(ListDockerImageHistory),
|
||||
ListDockerVolumes(ListDockerVolumes),
|
||||
InspectDockerVolume(InspectDockerVolume),
|
||||
#[serde(alias = "ListDockerNetworks")]
|
||||
ListNetworks(ListNetworks),
|
||||
#[serde(alias = "InspectDockerNetwork")]
|
||||
InspectNetwork(InspectNetwork),
|
||||
#[serde(alias = "ListDockerImages")]
|
||||
ListImages(ListImages),
|
||||
#[serde(alias = "InspectDockerImage")]
|
||||
InspectImage(InspectImage),
|
||||
#[serde(alias = "ListDockerImageHistory")]
|
||||
ListImageHistory(ListImageHistory),
|
||||
#[serde(alias = "ListDockerVolumes")]
|
||||
ListVolumes(ListVolumes),
|
||||
#[serde(alias = "InspectDockerVolume")]
|
||||
InspectVolume(InspectVolume),
|
||||
|
||||
// ==== SERVER STATS ====
|
||||
GetSystemInformation(GetSystemInformation),
|
||||
@@ -151,6 +170,7 @@ enum ReadRequest {
|
||||
ListStacks(ListStacks),
|
||||
ListFullStacks(ListFullStacks),
|
||||
ListStackServices(ListStackServices),
|
||||
ListAllStackServices(ListAllStackServices),
|
||||
ListCommonStackExtraArgs(ListCommonStackExtraArgs),
|
||||
ListCommonStackBuildExtraArgs(ListCommonStackBuildExtraArgs),
|
||||
|
||||
@@ -258,8 +278,10 @@ enum ReadRequest {
|
||||
// ==== PROVIDER ====
|
||||
GetGitProviderAccount(GetGitProviderAccount),
|
||||
ListGitProviderAccounts(ListGitProviderAccounts),
|
||||
GetDockerRegistryAccount(GetDockerRegistryAccount),
|
||||
ListDockerRegistryAccounts(ListDockerRegistryAccounts),
|
||||
#[serde(alias = "GetDockerRegistryAccount")]
|
||||
GetImageRegistryAccount(GetImageRegistryAccount),
|
||||
#[serde(alias = "ListDockerRegistryAccounts")]
|
||||
ListImageRegistryAccounts(ListImageRegistryAccounts),
|
||||
|
||||
// ==== ONBOARDING KEY ====
|
||||
ListOnboardingKeys(ListOnboardingKeys),
|
||||
@@ -291,18 +313,28 @@ async fn handler(
|
||||
Json(request): Json<ReadRequest>,
|
||||
) -> mogh_error::Result<axum::response::Response> {
|
||||
let req_id = Uuid::new_v4();
|
||||
let variant: ReadRequestVariant = (&request).into();
|
||||
let method: ReadRequestMethod = (&request).into();
|
||||
|
||||
let user_id = user.id.clone();
|
||||
let username = user.username.clone();
|
||||
|
||||
trace!(
|
||||
"READ REQUEST {req_id} | METHOD: {variant} | USER: {} ({})",
|
||||
user.username, user.id
|
||||
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 {
|
||||
trace!(
|
||||
"READ REQUEST {req_id} | METHOD: {variant} | ERROR: {:#}",
|
||||
req_id = req_id.to_string(),
|
||||
method = method.to_string(),
|
||||
user_id,
|
||||
username,
|
||||
"READ REQUEST | ERROR: {:#}",
|
||||
e.error
|
||||
);
|
||||
}
|
||||
@@ -344,6 +376,7 @@ impl Resolve<ReadArgs> for GetCoreInfo {
|
||||
disable_websocket_reconnect: config.disable_websocket_reconnect,
|
||||
enable_fancy_toml: config.enable_fancy_toml,
|
||||
timezone: config.timezone.clone(),
|
||||
default_pagination_limit: config.default_pagination_limit,
|
||||
public_key: core_keys().load().public.to_string(),
|
||||
};
|
||||
Ok(info)
|
||||
@@ -369,7 +402,9 @@ impl Resolve<ReadArgs> for ListSecrets {
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => None,
|
||||
BuilderConfig::Server(config) => Some(config.server_id),
|
||||
BuilderConfig::Server(config) => {
|
||||
config.server_ids.first().cloned()
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
secrets.extend(config.secrets);
|
||||
None
|
||||
@@ -424,11 +459,13 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => {}
|
||||
BuilderConfig::Server(config) => {
|
||||
merge_git_providers_for_server(
|
||||
&mut providers,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
if let Some(server_id) = config.server_ids.first() {
|
||||
merge_git_providers_for_server(
|
||||
&mut providers,
|
||||
server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_git_providers(
|
||||
@@ -450,18 +487,24 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
let (builds, repos, syncs) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
@@ -513,33 +556,35 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerRegistriesFromConfig {
|
||||
impl Resolve<ReadArgs> for ListImageRegistriesFromConfig {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerRegistriesFromConfigResponse> {
|
||||
let mut registries = core_config().docker_registries.clone();
|
||||
) -> mogh_error::Result<ListImageRegistriesFromConfigResponse> {
|
||||
let mut registries = core_config().image_registries.clone();
|
||||
|
||||
if let Some(target) = self.target {
|
||||
match target {
|
||||
ResourceTarget::Server(id) => {
|
||||
merge_docker_registries_for_server(&mut registries, &id)
|
||||
merge_image_registries_for_server(&mut registries, &id)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => {}
|
||||
BuilderConfig::Server(config) => {
|
||||
merge_docker_registries_for_server(
|
||||
&mut registries,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
if let Some(server_id) = config.server_ids.first() {
|
||||
merge_image_registries_for_server(
|
||||
&mut registries,
|
||||
server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_docker_registries(
|
||||
merge_image_registries(
|
||||
&mut registries,
|
||||
config.docker_registries,
|
||||
config.image_registries,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -597,14 +642,14 @@ fn merge_git_providers(
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge_docker_registries_for_server(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
async fn merge_image_registries_for_server(
|
||||
registries: &mut Vec<ImageRegistry>,
|
||||
server_id: &str,
|
||||
) -> mogh_error::Result<()> {
|
||||
let server = resource::get::<Server>(server_id).await?;
|
||||
let more = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery_client::api::ListDockerRegistries {})
|
||||
.request(periphery_client::api::ListImageRegistries {})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -612,13 +657,13 @@ async fn merge_docker_registries_for_server(
|
||||
server.name
|
||||
)
|
||||
})?;
|
||||
merge_docker_registries(registries, more);
|
||||
merge_image_registries(registries, more);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_docker_registries(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
more: Vec<DockerRegistry>,
|
||||
fn merge_image_registries(
|
||||
registries: &mut Vec<ImageRegistry>,
|
||||
more: Vec<ImageRegistry>,
|
||||
) {
|
||||
for incoming_registry in more {
|
||||
if let Some(registry) = registries
|
||||
|
||||
@@ -3,19 +3,21 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
procedure::{Procedure, ProcedureState},
|
||||
procedure::{
|
||||
Procedure, ProcedureListItem, ProcedureSortBy, ProcedureState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_all_tags,
|
||||
helpers::query::{get_all_tags, get_procedure_state},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
state::{action_states, procedure_state_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetProcedure {
|
||||
async fn resolve(
|
||||
@@ -43,15 +45,45 @@ impl Resolve<ReadArgs> for ListProcedures {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Procedure>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ProcedureListItem> =
|
||||
match self.sort_by {
|
||||
ProcedureSortBy::Name => resource::ListItemSort::Name,
|
||||
ProcedureSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ProcedureSortBy::NextRun => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.next_scheduled_run
|
||||
.cmp(&b.info.next_scheduled_run)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let procedures = resource::list_items_for_user::<Procedure>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|procedure| {
|
||||
states.is_empty() || states.contains(&procedure.info.state)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(procedures)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +97,29 @@ impl Resolve<ReadArgs> for ListFullProcedures {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Procedure>(
|
||||
resource::list_full_for_user_filtered::<Procedure, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|procedure| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_procedure_state(&procedure.id).await)
|
||||
{
|
||||
Some(procedure)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -84,6 +133,8 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
|
||||
) -> mogh_error::Result<GetProceduresSummaryResponse> {
|
||||
let procedures = resource::list_full_for_user::<Procedure>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -61,11 +61,11 @@ impl Resolve<ReadArgs> for ListGitProviderAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetDockerRegistryAccount {
|
||||
impl Resolve<ReadArgs> for GetImageRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetDockerRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<GetImageRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can read docker registry accounts")
|
||||
@@ -83,11 +83,11 @@ impl Resolve<ReadArgs> for GetDockerRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerRegistryAccounts {
|
||||
impl Resolve<ReadArgs> for ListImageRegistryAccounts {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerRegistryAccountsResponse> {
|
||||
) -> mogh_error::Result<ListImageRegistryAccountsResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can read docker registry accounts")
|
||||
|
||||
@@ -3,7 +3,9 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
repo::{Repo, RepoActionState, RepoListItem, RepoState},
|
||||
repo::{
|
||||
Repo, RepoActionState, RepoListItem, RepoSortBy, RepoState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -15,7 +17,7 @@ use crate::{
|
||||
state::{action_states, repo_state_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetRepo {
|
||||
async fn resolve(
|
||||
@@ -43,15 +45,41 @@ impl Resolve<ReadArgs> for ListRepos {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Repo>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<RepoListItem> =
|
||||
match self.sort_by {
|
||||
RepoSortBy::Name => resource::ListItemSort::Name,
|
||||
RepoSortBy::Repo => {
|
||||
resource::ListItemSort::DbField("config.repo")
|
||||
}
|
||||
RepoSortBy::Branch => {
|
||||
resource::ListItemSort::DbField("config.branch")
|
||||
}
|
||||
RepoSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let repos = resource::list_items_for_user::<Repo>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|repo| states.is_empty() || states.contains(&repo.info.state),
|
||||
)
|
||||
.await?;
|
||||
Ok(repos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +93,29 @@ impl Resolve<ReadArgs> for ListFullRepos {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Repo>(
|
||||
resource::list_full_for_user_filtered::<Repo, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|repo| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&resource::get_repo_state(&repo.id).await)
|
||||
{
|
||||
Some(repo)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -105,6 +150,8 @@ impl Resolve<ReadArgs> for GetReposSummary {
|
||||
) -> mogh_error::Result<GetReposSummaryResponse> {
|
||||
let repos = resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
action::Action,
|
||||
action::{Action, ActionQuerySpecifics},
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
procedure::{Procedure, ProcedureQuerySpecifics},
|
||||
resource::{ResourceQuery, TemplatesQueryBehavior},
|
||||
schedule::Schedule,
|
||||
schedule::{Schedule, ScheduleSortBy},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -18,7 +20,7 @@ use crate::{
|
||||
schedule::get_schedule_item_info,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for ListSchedules {
|
||||
async fn resolve(
|
||||
@@ -29,24 +31,36 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
let (actions, procedures) = tokio::try_join!(
|
||||
list_full_for_user::<Action>(
|
||||
ResourceQuery {
|
||||
names: Default::default(),
|
||||
templates: TemplatesQueryBehavior::Include,
|
||||
tag_behavior: self.tag_behavior,
|
||||
tags: self.tags.clone(),
|
||||
specific: Default::default(),
|
||||
terms: self.terms.clone(),
|
||||
specific: ActionQuerySpecifics {
|
||||
scheduled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
),
|
||||
list_full_for_user::<Procedure>(
|
||||
ResourceQuery {
|
||||
names: Default::default(),
|
||||
templates: TemplatesQueryBehavior::Include,
|
||||
tag_behavior: self.tag_behavior,
|
||||
tags: self.tags.clone(),
|
||||
specific: Default::default(),
|
||||
terms: self.terms,
|
||||
specific: ProcedureQuerySpecifics {
|
||||
scheduled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -96,12 +110,51 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
let (actions, procedures) =
|
||||
tokio::join!(join_all(actions), join_all(procedures));
|
||||
|
||||
Ok(
|
||||
actions
|
||||
.into_iter()
|
||||
.chain(procedures)
|
||||
.filter(|s| !s.schedule.is_empty())
|
||||
.collect(),
|
||||
)
|
||||
// The terms / scheduled filters are already applied
|
||||
// at the db level by the queries above.
|
||||
let mut schedules =
|
||||
actions.into_iter().chain(procedures).collect::<Vec<_>>();
|
||||
|
||||
// The schedules are composed in memory across resource types,
|
||||
// so all matching schedules are collected and sorted
|
||||
// before applying pagination.
|
||||
// All comparators fall back to name based sorting for equal
|
||||
// sort keys, inside `compare`, so descending sorts are fully
|
||||
// descending, matching the List<Resource> apis.
|
||||
let compare: fn(&Schedule, &Schedule) -> Ordering =
|
||||
match self.sort_by {
|
||||
ScheduleSortBy::Name => |a, b| a.name.cmp(&b.name),
|
||||
ScheduleSortBy::Schedule => |a, b| {
|
||||
a.schedule
|
||||
.cmp(&b.schedule)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
// Order unscheduled (None) last, matching the UI.
|
||||
ScheduleSortBy::NextRun => |a, b| {
|
||||
(a.next_scheduled_run.is_none(), a.next_scheduled_run)
|
||||
.cmp(&(
|
||||
b.next_scheduled_run.is_none(),
|
||||
b.next_scheduled_run,
|
||||
))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
ScheduleSortBy::Enabled => |a, b| {
|
||||
a.enabled.cmp(&b.enabled).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
};
|
||||
if self.sort_desc {
|
||||
schedules.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
schedules.sort_by(compare);
|
||||
}
|
||||
|
||||
let limit = list_limit(self.limit);
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(schedules.into_iter().skip(skip).take(take).collect())
|
||||
}
|
||||
}
|
||||
|
||||
+113
-546
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cmp,
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
@@ -15,47 +15,31 @@ use database::mungos::{
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
deployment::Deployment,
|
||||
docker::{
|
||||
container::{
|
||||
Container, ContainerListItem, ContainerStateStatusEnum,
|
||||
},
|
||||
image::{Image, ImageHistoryResponseItem},
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
server::{
|
||||
Server, ServerActionState, ServerListItem, ServerQuery,
|
||||
Server, ServerActionState, ServerListItem, ServerSortBy,
|
||||
ServerState,
|
||||
},
|
||||
stack::{Stack, StackServiceNames},
|
||||
stats::{SystemInformation, SystemProcess},
|
||||
update::Log,
|
||||
stats::{MinimalSystemStats, SystemInformation, SystemProcess},
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCode;
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api::{
|
||||
self as periphery,
|
||||
container::InspectContainer,
|
||||
docker::{
|
||||
ImageHistory, InspectImage, InspectNetwork, InspectVolume,
|
||||
},
|
||||
};
|
||||
use periphery_client::api as periphery;
|
||||
use reqwest::StatusCode;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
helpers::{periphery_client, query::get_all_tags},
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_cached_server_state},
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
stack::compose_container_match_regex,
|
||||
state::{action_states, db_client, server_status_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetServersSummary {
|
||||
async fn resolve(
|
||||
@@ -64,6 +48,8 @@ impl Resolve<ReadArgs> for GetServersSummary {
|
||||
) -> mogh_error::Result<GetServersSummaryResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -125,15 +111,89 @@ impl Resolve<ReadArgs> for ListServers {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Server>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ServerListItem> = match self
|
||||
.sort_by
|
||||
{
|
||||
ServerSortBy::Name => resource::ListItemSort::Name,
|
||||
ServerSortBy::Region => {
|
||||
resource::ListItemSort::DbField("config.region")
|
||||
}
|
||||
ServerSortBy::Version => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.version
|
||||
.cmp(&b.info.version)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ServerSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ServerSortBy::Cpu => stats_sort(|stats| stats.cpu_perc as f64),
|
||||
ServerSortBy::Memory => stats_sort(|stats| {
|
||||
usage_percent(stats.mem_used_gb, stats.mem_total_gb)
|
||||
}),
|
||||
ServerSortBy::Disk => stats_sort(|stats| {
|
||||
usage_percent(stats.disk_used_gb, stats.disk_total_gb)
|
||||
}),
|
||||
ServerSortBy::LoadAverage => {
|
||||
stats_sort(|stats| stats.load_average.one)
|
||||
}
|
||||
ServerSortBy::Network => stats_sort(|stats| {
|
||||
stats.network_ingress_bytes + stats.network_egress_bytes
|
||||
}),
|
||||
};
|
||||
let servers = resource::list_items_for_user::<Server>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|server| {
|
||||
states.is_empty() || states.contains(&server.info.state)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an in memory sort on the list item stats,
|
||||
/// matching the stats displayed on the server stats table.
|
||||
/// Servers without stats (unreachable / disabled) order last.
|
||||
fn stats_sort(
|
||||
metric: fn(&MinimalSystemStats) -> f64,
|
||||
) -> resource::ListItemSort<ServerListItem> {
|
||||
resource::ListItemSort::InMemory(Box::new(move |a, b| {
|
||||
match (a.info.stats.as_ref(), b.info.stats.as_ref()) {
|
||||
(Some(a_stats), Some(b_stats)) => {
|
||||
metric(a_stats).total_cmp(&metric(b_stats))
|
||||
}
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => Ordering::Equal,
|
||||
}
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
|
||||
fn usage_percent(used: f64, total: f64) -> f64 {
|
||||
if total > 0.0 {
|
||||
100.0 * used / total
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +207,29 @@ impl Resolve<ReadArgs> for ListFullServers {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Server>(
|
||||
resource::list_full_for_user_filtered::<Server, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|server| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_cached_server_state(&server.id).await)
|
||||
{
|
||||
Some(server)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -339,7 +416,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
let curr_ts = unix_timestamp_ms() as i64;
|
||||
let mut curr_ts = curr_ts
|
||||
- curr_ts % granularity
|
||||
- granularity * STATS_PER_PAGE * page as i64;
|
||||
- granularity * (page as i64).saturating_mul(STATS_PER_PAGE);
|
||||
for _ in 0..STATS_PER_PAGE {
|
||||
ts_vec.push(curr_ts);
|
||||
curr_ts -= granularity;
|
||||
@@ -353,7 +430,6 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
},
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "ts": -1 })
|
||||
.skip(page as u64 * STATS_PER_PAGE as u64)
|
||||
.limit(STATS_PER_PAGE)
|
||||
.build(),
|
||||
)
|
||||
@@ -368,512 +444,3 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerContainersResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.containers.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListAllDockerContainersResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
ServerQuery::builder().names(self.servers.clone()).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut containers = Vec::<ContainerListItem>::new();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
let Some(docker) = &cache.docker else {
|
||||
continue;
|
||||
};
|
||||
let more = docker
|
||||
.containers
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
self.containers.is_empty()
|
||||
|| self.containers.contains(&container.name)
|
||||
})
|
||||
.cloned();
|
||||
containers.extend(more);
|
||||
}
|
||||
|
||||
Ok(containers)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetDockerContainersSummaryResponse> {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get servers from db")?;
|
||||
|
||||
let mut res = GetDockerContainersSummaryResponse::default();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(docker) = &cache.docker {
|
||||
for container in &docker.containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
| ContainerStateStatusEnum::Paused
|
||||
| ContainerStateStatusEnum::Exited => res.stopped += 1,
|
||||
ContainerStateStatusEnum::Running => res.running += 1,
|
||||
ContainerStateStatusEnum::Empty => res.unknown += 1,
|
||||
_ => res.unhealthy += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Container> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.inspect(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect container: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectContainer {
|
||||
name: self.container,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LOG_LENGTH: u64 = 5000;
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let GetContainerLog {
|
||||
server,
|
||||
container,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLog {
|
||||
name: container,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let SearchContainerLog {
|
||||
server,
|
||||
container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLogSearch {
|
||||
name: container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
// first check deployments
|
||||
if let Ok(deployment) =
|
||||
resource::get::<Deployment>(&self.container).await
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// then check stacks
|
||||
let stacks = list_resources_for_user::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check matching stack
|
||||
for stack in stacks {
|
||||
for StackServiceNames {
|
||||
service_name,
|
||||
container_name,
|
||||
..
|
||||
} in stack
|
||||
.info
|
||||
.deployed_services
|
||||
.unwrap_or(stack.info.latest_services)
|
||||
{
|
||||
let is_match = match compose_container_match_regex(&container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
continue;
|
||||
}
|
||||
}.is_match(&self.container);
|
||||
|
||||
if is_match {
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Stack(stack.id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetResourceMatchingContainerResponse { resource: None })
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerNetworks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerNetworksResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.networks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerNetwork {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Network> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect network: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectNetwork { name: self.network })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerImages {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerImagesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.images.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerImage {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Image> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect image: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectImage { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerImageHistory {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot get image history: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(ImageHistory { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerVolumes {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerVolumesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.volumes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerVolume {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Volume> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectVolume { name: self.volume })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListComposeProjectsResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.projects.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl Resolve<ReadArgs> for ListAllTerminals {
|
||||
// async fn resolve(
|
||||
// self,
|
||||
// args: &ReadArgs,
|
||||
// ) -> Result<Self::Response, Self::Error> {
|
||||
// // match self.tar
|
||||
// let mut terminals = resource::list_full_for_user::<Server>(
|
||||
// self.query, &args.user, &all_tags,
|
||||
// )
|
||||
// .await?
|
||||
// .into_iter()
|
||||
// .map(|server| async move {
|
||||
// (
|
||||
// list_terminals_inner(&server, self.fresh).await,
|
||||
// (server.id, server.name),
|
||||
// )
|
||||
// })
|
||||
// .collect::<FuturesUnordered<_>>()
|
||||
// .collect::<Vec<_>>()
|
||||
// .await
|
||||
// .into_iter()
|
||||
// .flat_map(|(terminals, server)| {
|
||||
// let terminals = terminals.ok()?;
|
||||
// Some((terminals, server))
|
||||
// })
|
||||
// .flat_map(|(terminals, (server_id, server_name))| {
|
||||
// terminals.into_iter().map(move |info| {
|
||||
// TerminalInfoWithServer::from_terminal_info(
|
||||
// &server_id,
|
||||
// &server_name,
|
||||
// info,
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// terminals.sort_by(|a, b| {
|
||||
// a.server_name.cmp(&b.server_name).then(a.name.cmp(&b.name))
|
||||
// });
|
||||
|
||||
// Ok(terminals)
|
||||
// }
|
||||
// }
|
||||
|
||||
+205
-61
@@ -9,7 +9,10 @@ use komodo_client::{
|
||||
container::Container, service::SwarmService, stack::SwarmStack,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
stack::{Stack, StackActionState, StackListItem, StackState},
|
||||
stack::{
|
||||
Stack, StackActionState, StackListItem, StackQuery,
|
||||
StackService, StackSortBy, StackState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCodeError as _;
|
||||
@@ -22,7 +25,9 @@ use reqwest::StatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client, query::get_all_tags, swarm::swarm_request,
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_cached_stack_state},
|
||||
swarm::swarm_request,
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
@@ -30,7 +35,7 @@ use crate::{
|
||||
state::{action_states, stack_status_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetStack {
|
||||
async fn resolve(
|
||||
@@ -48,6 +53,128 @@ impl Resolve<ReadArgs> for GetStack {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<StackListItem>> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<StackListItem> =
|
||||
match self.sort_by {
|
||||
StackSortBy::Name => resource::ListItemSort::Name,
|
||||
StackSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
StackSortBy::Host => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
let host_a = if a.info.swarm_id.is_empty() {
|
||||
&a.info.server_name
|
||||
} else {
|
||||
&a.info.swarm_name
|
||||
};
|
||||
let host_b = if b.info.swarm_id.is_empty() {
|
||||
&b.info.server_name
|
||||
} else {
|
||||
&b.info.swarm_name
|
||||
};
|
||||
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
StackSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| {
|
||||
// Use ! with update available to order 'true' first
|
||||
(!a.info.update_available())
|
||||
.cmp(&!b.info.update_available())
|
||||
})
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let stacks = resource::list_items_for_user::<Stack>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|stack| {
|
||||
(!only_update_available
|
||||
|| stack
|
||||
.info
|
||||
.services
|
||||
.iter()
|
||||
.any(|service| service.update_available))
|
||||
&& (states.is_empty() || states.contains(&stack.info.state))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(stacks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListFullStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListFullStacksResponse> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Stack, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|stack| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_cached_stack_state(&stack.id).await)
|
||||
{
|
||||
Some(stack)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStackServices {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -72,6 +199,75 @@ impl Resolve<ReadArgs> for ListStackServices {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllStackServices {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListStackServicesResponse> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_for_user::<Stack>(
|
||||
StackQuery::builder()
|
||||
.names(self.stacks.clone())
|
||||
.tags(self.tags)
|
||||
.build(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut services = Vec::<StackService>::new();
|
||||
let mut skipped = 0;
|
||||
let limit = list_limit(self.limit);
|
||||
let limit_usize = limit as usize;
|
||||
// Eg. page 1 skips until after 100 services, page 2 after 200.
|
||||
let skip = limit.saturating_mul(self.page);
|
||||
// Match terms case insensitively.
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for stack in stacks {
|
||||
let cache =
|
||||
stack_status_cache().get_or_insert_default(&stack.id).await;
|
||||
let more = cache.curr.services
|
||||
.iter()
|
||||
.filter(|service| {
|
||||
// Apply state filter if defined.
|
||||
(self.state.is_empty() || self.state.contains(&service.state)) &&
|
||||
// Apply terms filter if defined
|
||||
(terms.is_empty()
|
||||
// Match when all terms contained within a name.
|
||||
|| {
|
||||
let name = service.service.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
})
|
||||
});
|
||||
for service in more {
|
||||
if skipped < skip {
|
||||
skipped += 1;
|
||||
} else {
|
||||
// push and maybe early return
|
||||
services.push(service.clone());
|
||||
if limit > 0 && services.len() >= limit_usize {
|
||||
return Ok(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetStackLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -339,6 +535,8 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -373,6 +571,8 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -395,64 +595,6 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<StackListItem>> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let stacks = resource::list_for_user::<Stack>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
let stacks = if only_update_available {
|
||||
stacks
|
||||
.into_iter()
|
||||
.filter(|stack| {
|
||||
stack
|
||||
.info
|
||||
.services
|
||||
.iter()
|
||||
.any(|service| service.update_available)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
stacks
|
||||
};
|
||||
Ok(stacks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListFullStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListFullStacksResponse> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetStackActionState {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -481,6 +623,8 @@ impl Resolve<ReadArgs> for GetStacksSummary {
|
||||
) -> mogh_error::Result<GetStacksSummaryResponse> {
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -3,7 +3,9 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
|
||||
swarm::{
|
||||
Swarm, SwarmActionState, SwarmListItem, SwarmSortBy, SwarmState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -15,7 +17,7 @@ use crate::{
|
||||
state::{action_states, server_status_cache, swarm_status_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarm {
|
||||
async fn resolve(
|
||||
@@ -43,12 +45,32 @@ impl Resolve<ReadArgs> for ListSwarms {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<SwarmListItem> =
|
||||
match self.sort_by {
|
||||
SwarmSortBy::Name => resource::ListItemSort::Name,
|
||||
SwarmSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Swarm>(
|
||||
resource::list_items_for_user::<Swarm>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -65,9 +87,12 @@ impl Resolve<ReadArgs> for ListFullSwarms {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Swarm>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -105,6 +130,8 @@ impl Resolve<ReadArgs> for GetSwarmsSummary {
|
||||
) -> mogh_error::Result<GetSwarmsSummaryResponse> {
|
||||
let swarms = resource::list_full_for_user::<Swarm>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -5,6 +5,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
sync::{
|
||||
ResourceSync, ResourceSyncActionState, ResourceSyncListItem,
|
||||
ResourceSyncSortBy,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -15,7 +16,7 @@ use crate::{
|
||||
resource, state::action_states,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceSync {
|
||||
async fn resolve(
|
||||
@@ -43,12 +44,52 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ResourceSyncListItem> =
|
||||
match self.sort_by {
|
||||
ResourceSyncSortBy::Name => resource::ListItemSort::Name,
|
||||
ResourceSyncSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ResourceSyncSortBy::Branch => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.branch
|
||||
.cmp(&b.info.branch)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ResourceSyncSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
resource::list_items_for_user::<ResourceSync>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -65,9 +106,12 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -106,6 +150,8 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
|
||||
let resource_syncs =
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -120,7 +166,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())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use futures_util::{
|
||||
FutureExt, StreamExt as _, stream::FuturesUnordered,
|
||||
@@ -9,7 +11,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{Terminal, TerminalTarget},
|
||||
terminal::{Terminal, TerminalSortBy, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
@@ -22,7 +24,7 @@ use crate::{
|
||||
resource,
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
use super::{ReadArgs, list_limit};
|
||||
|
||||
//
|
||||
|
||||
@@ -31,57 +33,111 @@ impl Resolve<ReadArgs> for ListTerminals {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListTerminalsResponse> {
|
||||
let Some(target) = self.target else {
|
||||
return list_all_terminals_for_user(user, self.use_names).await;
|
||||
let mut terminals = match self.target {
|
||||
None => {
|
||||
list_all_terminals_for_user(user, self.use_names).await?
|
||||
}
|
||||
Some(target) => match &target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
},
|
||||
};
|
||||
match &target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
|
||||
// The terminals come from Periphery agents rather than the db,
|
||||
// so the terms filter / sort / pagination are applied in memory.
|
||||
if !self.terms.is_empty() {
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
terminals.retain(|terminal| {
|
||||
let name = terminal.name.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
});
|
||||
}
|
||||
|
||||
// All comparators fall back to name based sorting for equal
|
||||
// sort keys, inside `compare`, so descending sorts are fully
|
||||
// descending, matching the List<Resource> apis.
|
||||
let compare: fn(&Terminal, &Terminal) -> Ordering =
|
||||
match self.sort_by {
|
||||
TerminalSortBy::Name => |a, b| a.name.cmp(&b.name),
|
||||
TerminalSortBy::Target => |a, b| {
|
||||
a.target.cmp(&b.target).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Command => |a, b| {
|
||||
a.command.cmp(&b.command).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Size => |a, b| {
|
||||
a.stored_size_kb
|
||||
.total_cmp(&b.stored_size_kb)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Created => |a, b| {
|
||||
a.created_at
|
||||
.cmp(&b.created_at)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
};
|
||||
if self.sort_desc {
|
||||
terminals.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
terminals.sort_by(compare);
|
||||
}
|
||||
|
||||
let limit = list_limit(self.limit);
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(terminals.into_iter().skip(skip).take(take).collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +148,8 @@ async fn list_all_terminals_for_user(
|
||||
let (mut servers, stacks, deployments) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
@@ -103,12 +161,16 @@ async fn list_all_terminals_for_user(
|
||||
.collect::<Vec<_>>())),
|
||||
resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
@@ -139,7 +201,7 @@ async fn list_all_terminals_for_user(
|
||||
}
|
||||
}
|
||||
|
||||
let mut terminals = servers
|
||||
let terminals = servers
|
||||
.into_iter()
|
||||
.map(|(server, server_permission)| async move {
|
||||
(
|
||||
@@ -168,6 +230,7 @@ async fn list_all_terminals_for_user(
|
||||
server_id.clone()
|
||||
}),
|
||||
};
|
||||
terminal.target_name = Some(server_name.clone());
|
||||
terminal
|
||||
}),
|
||||
TerminalTarget::Container { container, .. } => {
|
||||
@@ -180,6 +243,7 @@ async fn list_all_terminals_for_user(
|
||||
},
|
||||
container,
|
||||
};
|
||||
terminal.target_name = Some(server_name.clone());
|
||||
terminal
|
||||
})
|
||||
}
|
||||
@@ -193,6 +257,7 @@ async fn list_all_terminals_for_user(
|
||||
},
|
||||
service,
|
||||
};
|
||||
terminal.target_name = Some(s.name.clone());
|
||||
terminal
|
||||
})
|
||||
}
|
||||
@@ -206,6 +271,7 @@ async fn list_all_terminals_for_user(
|
||||
d.id.clone()
|
||||
},
|
||||
};
|
||||
terminal.target_name = Some(d.name.clone());
|
||||
terminal
|
||||
},
|
||||
)
|
||||
@@ -220,10 +286,6 @@ async fn list_all_terminals_for_user(
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
terminals.sort_by(|a, b| {
|
||||
a.target.cmp(&b.target).then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
Ok(terminals)
|
||||
}
|
||||
|
||||
|
||||
+123
-340
@@ -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,
|
||||
@@ -25,6 +28,7 @@ use crate::{
|
||||
resource,
|
||||
state::db_client,
|
||||
sync::{
|
||||
replace_ids::ReplaceIds,
|
||||
toml::{ToToml, convert_resource},
|
||||
user_groups::{convert_user_groups, user_group_to_toml},
|
||||
variables::variable_to_toml,
|
||||
@@ -38,122 +42,47 @@ 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(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::$Type(resource.id)),
|
||||
);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
extend_targets!(
|
||||
Alerter,
|
||||
Builder,
|
||||
Server,
|
||||
Swarm,
|
||||
Stack,
|
||||
Deployment,
|
||||
Build,
|
||||
Repo,
|
||||
Procedure,
|
||||
Action,
|
||||
ResourceSync,
|
||||
);
|
||||
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
@@ -187,6 +116,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets,
|
||||
user_groups,
|
||||
include_variables: self.include_variables,
|
||||
existing: self.existing,
|
||||
}
|
||||
.resolve(args)
|
||||
.await
|
||||
@@ -202,131 +132,64 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
targets,
|
||||
user_groups,
|
||||
include_variables,
|
||||
existing,
|
||||
} = self;
|
||||
let mut res = ResourcesToml::default();
|
||||
let id_to_tags = get_id_to_tags(None).await?;
|
||||
let ReadArgs { user } = args;
|
||||
macro_rules! convert_target {
|
||||
($id:expr, $Type:ident, $field:ident) => {{
|
||||
let mut resource = get_check_permissions::<$Type>(
|
||||
&$id,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
$Type::replace_ids(&mut resource.config);
|
||||
let (deploy, after) = existing
|
||||
.as_ref()
|
||||
.and_then(|e| {
|
||||
e.$field.iter().find(|r| r.name == resource.name)
|
||||
})
|
||||
.map(|r| (r.deploy, r.after.clone()))
|
||||
.unwrap_or_default();
|
||||
res.$field.push(convert_resource::<$Type>(
|
||||
resource,
|
||||
deploy,
|
||||
after,
|
||||
&id_to_tags,
|
||||
));
|
||||
}};
|
||||
}
|
||||
for target in targets {
|
||||
match target {
|
||||
ResourceTarget::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>(
|
||||
@@ -340,7 +203,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
|| !sync.config.repo.is_empty()
|
||||
|| !sync.config.linked_repo.is_empty())
|
||||
{
|
||||
ResourceSync::replace_ids(&mut sync);
|
||||
ResourceSync::replace_ids(&mut sync.config);
|
||||
res.resource_syncs.push(convert_resource::<ResourceSync>(
|
||||
sync,
|
||||
false,
|
||||
@@ -349,36 +212,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 +221,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 +270,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() {
|
||||
|
||||
@@ -47,7 +47,9 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
query,
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.skip(self.page as u64 * UPDATES_PER_PAGE as u64)
|
||||
.skip(
|
||||
(self.page as u64).saturating_mul(UPDATES_PER_PAGE as u64),
|
||||
)
|
||||
.limit(UPDATES_PER_PAGE)
|
||||
.build(),
|
||||
)
|
||||
|
||||
@@ -71,9 +71,20 @@ impl Resolve<ReadArgs> for ListUsers {
|
||||
anyhow!("this route is only accessable by admins").into(),
|
||||
);
|
||||
}
|
||||
let filter = match self.service_users {
|
||||
komodo_client::api::read::ServiceUserQueryBehavior::Include => {
|
||||
None
|
||||
}
|
||||
komodo_client::api::read::ServiceUserQueryBehavior::Exclude => {
|
||||
Some(doc! { "config.type": { "$ne": "Service" } })
|
||||
}
|
||||
komodo_client::api::read::ServiceUserQueryBehavior::Only => {
|
||||
Some(doc! { "config.type": "Service" })
|
||||
}
|
||||
};
|
||||
let mut users = find_collect(
|
||||
&db_client().users,
|
||||
None,
|
||||
filter,
|
||||
FindOptions::builder().sort(doc! { "username": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mongo_indexed::doc;
|
||||
use database::mungos::mongodb::bson::to_document;
|
||||
use database::{
|
||||
mongo_indexed::doc, mungos::mongodb::bson::oid::ObjectId,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
@@ -15,7 +13,6 @@ use komodo_client::{
|
||||
builder::{Builder, BuilderConfig},
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
server::ServerState,
|
||||
update::Update,
|
||||
},
|
||||
};
|
||||
@@ -25,12 +22,11 @@ use periphery_client::api::build::{
|
||||
};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::helpers::builder::connect_builder_periphery;
|
||||
use crate::{
|
||||
config::core_config,
|
||||
connection::PeripheryConnectionArgs,
|
||||
helpers::{
|
||||
git_token, periphery_client,
|
||||
query::get_server_with_state,
|
||||
git_token,
|
||||
update::{add_update, make_update},
|
||||
},
|
||||
periphery::PeripheryClient,
|
||||
@@ -476,48 +472,15 @@ async fn get_on_host_periphery(
|
||||
.await
|
||||
.context("Failed to get builder")?;
|
||||
|
||||
match builder.config {
|
||||
BuilderConfig::Aws(_) => {
|
||||
Err(anyhow!("Files on host doesn't work with AWS builder"))
|
||||
}
|
||||
BuilderConfig::Url(config) => {
|
||||
// TODO: Ensure connection is actually established.
|
||||
// Builder id no good because it may be active for multiple connections.
|
||||
let periphery = PeripheryClient::new(
|
||||
PeripheryConnectionArgs::from_url_builder(
|
||||
&ObjectId::new().to_hex(),
|
||||
&config,
|
||||
),
|
||||
config.insecure_tls,
|
||||
)
|
||||
.await?;
|
||||
// Poll for connection to be estalished
|
||||
let mut err = None;
|
||||
for _ in 0..10 {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
match periphery.health_check().await {
|
||||
Ok(_) => return Ok(periphery),
|
||||
Err(e) => err = Some(e),
|
||||
};
|
||||
}
|
||||
Err(err.context("Missing error")?)
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_id.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Builder is type server, but has no server attached"
|
||||
));
|
||||
}
|
||||
let (server, state) =
|
||||
get_server_with_state(&config.server_id).await?;
|
||||
if state != ServerState::Ok {
|
||||
return Err(anyhow!(
|
||||
"Builder server is disabled or not reachable"
|
||||
));
|
||||
};
|
||||
periphery_client(&server).await
|
||||
}
|
||||
if let BuilderConfig::Aws(_) = builder.config {
|
||||
return Err(anyhow!(
|
||||
"Files on host doesn't work with AWS builder"
|
||||
));
|
||||
}
|
||||
|
||||
connect_builder_periphery(build.name.clone(), None, builder, None)
|
||||
.await
|
||||
.map(|(periphery, _)| periphery)
|
||||
}
|
||||
|
||||
/// The successful case will be included as Some(remote_contents).
|
||||
|
||||
@@ -15,6 +15,7 @@ use komodo_client::{
|
||||
docker::container::RestartPolicyNameEnum,
|
||||
komodo_timestamp, optional_string,
|
||||
permission::PermissionLevel,
|
||||
resource::ResourceQuery,
|
||||
server::{Server, ServerState},
|
||||
to_container_compatible_name,
|
||||
update::Update,
|
||||
@@ -30,7 +31,9 @@ use crate::{
|
||||
api::execute::{self, ExecuteRequest, ExecutionResult},
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_deployment_state, get_swarm_or_server},
|
||||
query::{
|
||||
get_all_tags, get_deployment_state, get_swarm_or_server,
|
||||
},
|
||||
registry_token,
|
||||
update::{add_update, make_update, poll_update_until_complete},
|
||||
},
|
||||
@@ -300,10 +303,24 @@ impl Resolve<WriteArgs> for RenameDeployment {
|
||||
|
||||
let name = to_container_compatible_name(&self.name);
|
||||
|
||||
let container_state =
|
||||
get_deployment_state(&deployment.id).await?;
|
||||
let state = get_deployment_state(&deployment.id).await?;
|
||||
|
||||
if container_state == DeploymentState::Unknown {
|
||||
// When no custom name is configured, the container / service
|
||||
// follows the Deployment name, and must be kept matched:
|
||||
// - Server mode: rename the container to the new name.
|
||||
// - Swarm mode: services cannot be renamed, so the current
|
||||
// service name is pinned in info.deployed_name, and stays
|
||||
// in use until the next deploy recreates the service.
|
||||
// A configured custom name is unaffected by Deployment rename.
|
||||
let follows_name =
|
||||
deployment.config.custom_name.trim().is_empty();
|
||||
let deployed = state != DeploymentState::NotDeployed;
|
||||
|
||||
let rename_container = follows_name
|
||||
&& deployed
|
||||
&& !deployment.config.server_id.is_empty();
|
||||
|
||||
if rename_container && state == DeploymentState::Unknown {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot rename Deployment when container status is unknown"
|
||||
@@ -312,27 +329,51 @@ impl Resolve<WriteArgs> for RenameDeployment {
|
||||
);
|
||||
}
|
||||
|
||||
// Includes the Unknown state, in case a service
|
||||
// is still running under the current name.
|
||||
let pin_deployed_name = follows_name
|
||||
&& deployed
|
||||
&& !deployment.config.swarm_id.is_empty()
|
||||
&& deployment.info.deployed_name.is_empty();
|
||||
|
||||
let mut update =
|
||||
make_update(&deployment, Operation::RenameDeployment, user);
|
||||
|
||||
let mut set =
|
||||
doc! { "name": &name, "updated_at": komodo_timestamp() };
|
||||
if rename_container {
|
||||
// Container is renamed to the new name below.
|
||||
set.insert("info.deployed_name", name.as_str());
|
||||
} else if pin_deployed_name {
|
||||
set.insert("info.deployed_name", deployment.deployed_name());
|
||||
}
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().deployments,
|
||||
&deployment.id,
|
||||
database::mungos::update::Update::Set(
|
||||
doc! { "name": &name, "updated_at": komodo_timestamp() },
|
||||
),
|
||||
database::mungos::update::Update::Set(set),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("Failed to update Deployment name on db")?;
|
||||
|
||||
if container_state != DeploymentState::NotDeployed {
|
||||
if pin_deployed_name {
|
||||
update.push_simple_log(
|
||||
"Pin Service Name",
|
||||
format!(
|
||||
"Swarm services cannot be renamed, so the current service name '{}' stays in use until the next deploy recreates the service under the new name.",
|
||||
deployment.deployed_name()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if rename_container {
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
let log = periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RenameContainer {
|
||||
curr_name: deployment.name.clone(),
|
||||
curr_name: deployment.deployed_name().to_string(),
|
||||
new_name: name.clone(),
|
||||
})
|
||||
.await
|
||||
@@ -436,7 +477,7 @@ pub async fn check_deployment_for_update_inner(
|
||||
update_available: false,
|
||||
});
|
||||
}
|
||||
let domain = extract_registry_domain(&image)?;
|
||||
let domain = extract_registry_domain(image)?;
|
||||
let account =
|
||||
optional_string(&deployment.config.image_registry_account);
|
||||
let token = if let Some(account) = &account {
|
||||
@@ -458,13 +499,14 @@ pub async fn check_deployment_for_update_inner(
|
||||
};
|
||||
|
||||
let latest_digest = image_digest_cache()
|
||||
.get(&swarm_or_server, &image, account, token)
|
||||
.get(swarm_or_server, image, account, token)
|
||||
.await?;
|
||||
|
||||
resource::update_info::<Deployment>(
|
||||
&deployment.id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: latest_digest.clone(),
|
||||
deployed_name: deployment.info.deployed_name.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -621,6 +663,7 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
|
||||
fields(
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
skip_auto_update = self.skip_auto_update,
|
||||
wait_for_auto_update = self.wait_for_auto_update,
|
||||
)
|
||||
@@ -629,12 +672,23 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let deployments = list_full_for_user_using_pattern::<Deployment>(
|
||||
&self.pattern,
|
||||
Default::default(),
|
||||
ResourceQuery {
|
||||
tags: self.tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&[],
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ pub struct WriteArgs {
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EnumDiscriminants,
|
||||
)]
|
||||
#[strum_discriminants(name(WriteRequestVariant), derive(Display))]
|
||||
#[strum_discriminants(name(WriteRequestMethod), derive(Display))]
|
||||
#[args(WriteArgs)]
|
||||
#[response(Response)]
|
||||
#[error(mogh_error::Error)]
|
||||
@@ -211,9 +211,12 @@ pub enum WriteRequest {
|
||||
CreateGitProviderAccount(CreateGitProviderAccount),
|
||||
UpdateGitProviderAccount(UpdateGitProviderAccount),
|
||||
DeleteGitProviderAccount(DeleteGitProviderAccount),
|
||||
CreateDockerRegistryAccount(CreateDockerRegistryAccount),
|
||||
UpdateDockerRegistryAccount(UpdateDockerRegistryAccount),
|
||||
DeleteDockerRegistryAccount(DeleteDockerRegistryAccount),
|
||||
#[serde(alias = "CreateDockerRegistryAccount")]
|
||||
CreateImageRegistryAccount(CreateImageRegistryAccount),
|
||||
#[serde(alias = "UpdateDockerRegistryAccount")]
|
||||
UpdateImageRegistryAccount(UpdateImageRegistryAccount),
|
||||
#[serde(alias = "DeleteDockerRegistryAccount")]
|
||||
DeleteImageRegistryAccount(DeleteImageRegistryAccount),
|
||||
|
||||
// ==== ALERT ====
|
||||
CloseAlert(CloseAlert),
|
||||
@@ -255,19 +258,35 @@ async fn task(
|
||||
request: WriteRequest,
|
||||
user: User,
|
||||
) -> mogh_error::Result<axum::response::Response> {
|
||||
let req_id = Uuid::new_v4();
|
||||
let variant: WriteRequestVariant = (&request).into();
|
||||
let task_id = Uuid::new_v4();
|
||||
let method: WriteRequestMethod = (&request).into();
|
||||
|
||||
info!(
|
||||
"WRITE REQUEST {req_id} | METHOD: {variant} | USER: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
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} | METHOD: {variant} | ERROR: {:#}",
|
||||
task_id = task_id.to_string(),
|
||||
method = method.to_string(),
|
||||
user_id,
|
||||
username,
|
||||
"WRITE REQUEST | ERROR: {:#}",
|
||||
e.error
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ use mogh_pki::EncodedKeyPair;
|
||||
use mogh_resolver::Resolve;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
use crate::{api::write::WriteArgs, state::db_client};
|
||||
use crate::{
|
||||
api::write::WriteArgs, helpers::query::get_all_tags,
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -53,6 +56,21 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
|
||||
)?
|
||||
.public
|
||||
.into_inner();
|
||||
let tags = if self.tags.is_empty() {
|
||||
self.tags
|
||||
} else {
|
||||
// fix_tags by ensuring existence, and force replace with tag name.
|
||||
let all_tags = get_all_tags(None).await?;
|
||||
self
|
||||
.tags
|
||||
.into_iter()
|
||||
.filter_map(|tag| {
|
||||
let tag =
|
||||
all_tags.iter().find(|t| t.id == tag || t.name == tag)?;
|
||||
Some(tag.name.clone())
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let onboarding_key = OnboardingKey {
|
||||
public_key,
|
||||
name: self.name,
|
||||
@@ -60,8 +78,8 @@ impl Resolve<WriteArgs> for CreateOnboardingKey {
|
||||
onboarded: Default::default(),
|
||||
created_at: komodo_timestamp(),
|
||||
expires: self.expires,
|
||||
tags: self.tags,
|
||||
fix_existing_servers: self.fix_existing_servers,
|
||||
tags,
|
||||
privileged: self.privileged,
|
||||
copy_server: self.copy_server,
|
||||
create_builder: self.create_builder,
|
||||
};
|
||||
@@ -139,11 +157,26 @@ 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(fix_existing_servers) = self.fix_existing_servers {
|
||||
update.insert("fix_existing_servers", fix_existing_servers);
|
||||
if let Some(privileged) = self.privileged {
|
||||
update.insert("privileged", privileged);
|
||||
}
|
||||
|
||||
if let Some(copy_server) = self.copy_server {
|
||||
|
||||
@@ -347,9 +347,9 @@ async fn extract_resource_target_with_validation(
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for swarms")?
|
||||
.context("No matching server found")?
|
||||
.context("No matching swarm found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Server, id))
|
||||
Ok((ResourceTargetVariant::Swarm, id))
|
||||
}
|
||||
ResourceTarget::Server(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
Operation, ResourceTarget,
|
||||
provider::{DockerRegistryAccount, GitProviderAccount},
|
||||
provider::{GitProviderAccount, ImageRegistryAccount},
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCodeError;
|
||||
@@ -248,7 +248,7 @@ impl Resolve<WriteArgs> for DeleteGitProviderAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
impl Resolve<WriteArgs> for CreateImageRegistryAccount {
|
||||
#[instrument(
|
||||
"CreateDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -261,7 +261,7 @@ impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<CreateDockerRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<CreateImageRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!(
|
||||
@@ -271,7 +271,7 @@ impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
);
|
||||
}
|
||||
|
||||
let mut account: DockerRegistryAccount = self.account.into();
|
||||
let mut account: ImageRegistryAccount = self.account.into();
|
||||
|
||||
if account.domain.is_empty() {
|
||||
return Err(
|
||||
@@ -326,7 +326,7 @@ impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
|
||||
#[instrument(
|
||||
"UpdateDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -340,7 +340,7 @@ impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
async fn resolve(
|
||||
mut self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<UpdateDockerRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<UpdateImageRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can update docker registry accounts")
|
||||
@@ -423,7 +423,7 @@ impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
|
||||
impl Resolve<WriteArgs> for DeleteImageRegistryAccount {
|
||||
#[instrument(
|
||||
"DeleteDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -435,7 +435,7 @@ impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<DeleteDockerRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<DeleteImageRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can delete docker registry accounts")
|
||||
|
||||
@@ -19,6 +19,7 @@ use komodo_client::{
|
||||
all_logs_success, komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
resource::ResourceQuery,
|
||||
stack::{Stack, StackInfo, StackServiceWithUpdate, StackState},
|
||||
update::Update,
|
||||
user::{auto_redeploy_user, stack_user, system_user},
|
||||
@@ -36,7 +37,7 @@ use crate::{
|
||||
api::execute::{self, ExecuteRequest, ExecutionResult},
|
||||
config::core_config,
|
||||
helpers::{
|
||||
query::get_swarm_or_server,
|
||||
query::{get_all_tags, get_swarm_or_server},
|
||||
stack_git_token, swarm_or_server_request,
|
||||
update::{add_update, make_update, poll_update_until_complete},
|
||||
},
|
||||
@@ -555,15 +556,25 @@ impl Resolve<WriteArgs> for RefreshStackCache {
|
||||
&mut services,
|
||||
) {
|
||||
warn!(
|
||||
"failed to extract stack services, things won't works correctly. stack: {} | {e:#}",
|
||||
stack.name
|
||||
stack = stack.id,
|
||||
stack_name = stack.name,
|
||||
"Failed to extract stack services | {e:#}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(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 {
|
||||
// ================
|
||||
@@ -766,12 +777,18 @@ pub async fn check_stack_for_update_inner(
|
||||
|
||||
if image.is_empty() ||
|
||||
// Images with a hardcoded digest can't have update.
|
||||
image.contains('@')
|
||||
image.contains('@') ||
|
||||
// Services explicitly excluded from global auto-update checks.
|
||||
// Manual checks should still evaluate all services.
|
||||
(wait_for_auto_update &&
|
||||
stack.config.auto_update_skip_services.contains(
|
||||
&service.service_name,
|
||||
))
|
||||
{
|
||||
service.image_digest = None;
|
||||
continue;
|
||||
}
|
||||
match cache.get(&swarm_or_server, image, None, None).await {
|
||||
match cache.get(swarm_or_server, image, None, None).await {
|
||||
Ok(digest) => service.image_digest = Some(digest),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
@@ -861,7 +878,7 @@ pub async fn check_stack_for_update_inner(
|
||||
};
|
||||
|
||||
service_with_update.update_available =
|
||||
latest_digest.update_available(¤t_digests);
|
||||
latest_digest.update_available(current_digests);
|
||||
|
||||
if service_with_update.update_available
|
||||
&& (skip_auto_update || !stack.config.auto_update)
|
||||
@@ -930,7 +947,10 @@ pub async fn check_stack_for_update_inner(
|
||||
.retain(|(stack_id, _)| stack_id != &stack.id)
|
||||
.await;
|
||||
|
||||
let deploy_services = if stack.config.auto_update_all_services {
|
||||
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
|
||||
@@ -1021,6 +1041,7 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
|
||||
fields(
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
skip_auto_update = self.skip_auto_update,
|
||||
wait_for_auto_update = self.wait_for_auto_update,
|
||||
)
|
||||
@@ -1029,12 +1050,23 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let stacks = list_full_for_user_using_pattern::<Stack>(
|
||||
&self.pattern,
|
||||
Default::default(),
|
||||
ResourceQuery {
|
||||
tags: self.tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&[],
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
+46
-105
@@ -26,7 +26,8 @@ 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,
|
||||
@@ -515,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(),
|
||||
@@ -775,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 {
|
||||
@@ -912,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,
|
||||
@@ -928,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();
|
||||
|
||||
@@ -940,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,
|
||||
|
||||
@@ -3,8 +3,12 @@ use futures_util::{StreamExt as _, stream::FuturesUnordered};
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
NoData, deployment::Deployment, permission::PermissionLevel,
|
||||
server::Server, stack::Stack, terminal::TerminalTarget,
|
||||
NoData,
|
||||
deployment::Deployment,
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{Terminal, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
@@ -47,30 +51,35 @@ impl Resolve<WriteArgs> for CreateTerminal {
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<NoData> {
|
||||
) -> mogh_error::Result<Terminal> {
|
||||
match self.target.clone() {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
create_server_terminal(self, server, user).await?;
|
||||
create_server_terminal(self, server, user)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
TerminalTarget::Container { server, container } => {
|
||||
create_container_terminal(self, server, container, user)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
TerminalTarget::Stack { stack, service } => {
|
||||
let service = service
|
||||
.context("Must provide 'target.params.service'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
create_stack_service_terminal(self, stack, service, user)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
create_deployment_terminal(self, deployment, user).await?;
|
||||
create_deployment_terminal(self, deployment, user)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
};
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +93,7 @@ async fn create_server_terminal(
|
||||
}: CreateTerminal,
|
||||
server: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<Terminal> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
@@ -94,7 +103,7 @@ async fn create_server_terminal(
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
periphery
|
||||
let mut terminal = periphery
|
||||
.request(api::terminal::CreateServerTerminal {
|
||||
name,
|
||||
command,
|
||||
@@ -103,7 +112,12 @@ async fn create_server_terminal(
|
||||
.await
|
||||
.context("Failed to create Server Terminal on Periphery")?;
|
||||
|
||||
Ok(())
|
||||
// Fix server terminal target with server id
|
||||
terminal.target = TerminalTarget::Server {
|
||||
server: Some(server.id),
|
||||
};
|
||||
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
async fn create_container_terminal(
|
||||
@@ -111,7 +125,7 @@ async fn create_container_terminal(
|
||||
server: String,
|
||||
container: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<Terminal> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
@@ -127,7 +141,7 @@ async fn create_stack_service_terminal(
|
||||
stack: String,
|
||||
service: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<Terminal> {
|
||||
let (_, periphery, container) =
|
||||
get_stack_service_periphery_container(&stack, &service, user)
|
||||
.await?;
|
||||
@@ -138,7 +152,7 @@ async fn create_deployment_terminal(
|
||||
req: CreateTerminal,
|
||||
deployment: String,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<Terminal> {
|
||||
let (_, periphery, container) =
|
||||
get_deployment_periphery_container(&deployment, user).await?;
|
||||
create_container_terminal_inner(req, &periphery, container).await
|
||||
@@ -157,10 +171,10 @@ impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
mut self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<NoData> {
|
||||
let server = match &self.target {
|
||||
let server = match &mut self.target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
@@ -181,27 +195,31 @@ impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
)
|
||||
.await?
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
TerminalTarget::Stack {
|
||||
stack: stack_id, ..
|
||||
} => {
|
||||
let stack = get_check_permissions::<Stack>(
|
||||
stack_id,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
.await?;
|
||||
// Must fix any incoming stack name to id
|
||||
*stack_id = stack.id;
|
||||
resource::get::<Server>(&stack.config.server_id).await?
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
TerminalTarget::Deployment {
|
||||
deployment: deployment_id,
|
||||
} => {
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
deployment_id,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
.await?;
|
||||
// Must fix any incoming deployment name to id
|
||||
*deployment_id = deployment.id;
|
||||
resource::get::<Server>(&deployment.config.server_id).await?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -275,6 +293,8 @@ impl Resolve<WriteArgs> for BatchDeleteAllTerminals {
|
||||
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&all_tags,
|
||||
|
||||
@@ -36,6 +36,15 @@ use super::WriteArgs;
|
||||
const RECENTLY_VIEWED_MAX: usize = 10;
|
||||
|
||||
impl Resolve<WriteArgs> for PushRecentlyViewed {
|
||||
#[instrument(
|
||||
"PushRecentlyViewed",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
user_id = user.id,
|
||||
resource = format!("{:?}", self.resource)
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user, .. }: &WriteArgs,
|
||||
@@ -79,6 +88,12 @@ impl Resolve<WriteArgs> for PushRecentlyViewed {
|
||||
//
|
||||
|
||||
impl Resolve<WriteArgs> for SetLastSeenUpdate {
|
||||
#[instrument(
|
||||
"SetLastSeenUpdate",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(user_id = user.id)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user, .. }: &WriteArgs,
|
||||
|
||||
@@ -24,6 +24,9 @@ use crate::{
|
||||
mod terminal;
|
||||
mod update;
|
||||
|
||||
pub(super) const WS_KEEP_ALIVE_INTERVAL: std::time::Duration =
|
||||
std::time::Duration::from_secs(30);
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
// Periphery facing
|
||||
|
||||
@@ -5,6 +5,7 @@ use axum::{
|
||||
response::IntoResponse,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use colored::Colorize;
|
||||
use futures_util::{SinkExt, StreamExt as _};
|
||||
use komodo_client::{
|
||||
api::terminal::ConnectTerminalQuery, entities::user::User,
|
||||
@@ -116,9 +117,25 @@ async fn forward_ws_channel(
|
||||
};
|
||||
|
||||
let periphery_to_core = async {
|
||||
let mut keep_alive =
|
||||
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
|
||||
loop {
|
||||
// Already adheres to cancellation token
|
||||
match periphery_receiver.recv().await {
|
||||
let recv = tokio::select! {
|
||||
// Websocket keep-alive ping
|
||||
_ = keep_alive.tick() => {
|
||||
if client_send
|
||||
.send(ws::Message::Ping(Bytes::new()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
recv = periphery_receiver.recv() => recv,
|
||||
};
|
||||
match recv {
|
||||
Ok(Ok(bytes)) => {
|
||||
if let Err(e) =
|
||||
client_send.send(ws::Message::Binary(bytes.into())).await
|
||||
@@ -128,14 +145,26 @@ async fn forward_ws_channel(
|
||||
};
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
let _ = client_send
|
||||
.send(ws::Message::text(format!("{e:#}")))
|
||||
.await;
|
||||
let message = format!("{}: {e:#}", "ERROR".red().bold());
|
||||
if message.contains("pty exited") {
|
||||
let _ = client_send
|
||||
.send(ws::Message::text(format!(
|
||||
"\n{} {}",
|
||||
"pty".bold(),
|
||||
"exited".red().bold()
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
let _ = client_send
|
||||
.send(ws::Message::text(message.replace('\n', "\r\n")))
|
||||
.await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ =
|
||||
client_send.send(ws::Message::text("STREAM EOF")).await;
|
||||
let _ = client_send
|
||||
.send(ws::Message::text("\r\n\nSTREAM EOF"))
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use axum::{
|
||||
extract::{WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use komodo_client::entities::{
|
||||
ResourceTarget, permission::PermissionLevel, user::User,
|
||||
@@ -38,10 +39,19 @@ pub async fn handler(
|
||||
let cancel_clone = cancel.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut keep_alive =
|
||||
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
|
||||
loop {
|
||||
// poll for updates off the receiver / await cancel.
|
||||
// poll for updates off the receiver / await cancel / send keep-alive ping.
|
||||
let update = select! {
|
||||
_ = cancel_clone.cancelled() => break,
|
||||
_ = keep_alive.tick() => {
|
||||
if ws_sender.send(Message::Ping(Bytes::new())).await.is_err() {
|
||||
cancel_clone.cancel();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
update = receiver.recv() => {update.expect("failed to recv update msg")}
|
||||
};
|
||||
|
||||
|
||||
@@ -205,6 +205,20 @@ impl AuthImpl for KomodoAuthImpl {
|
||||
core_config().disable_user_registration
|
||||
}
|
||||
|
||||
fn local_registration_disabled(&self) -> bool {
|
||||
let config = core_config();
|
||||
config
|
||||
.disable_local_user_registration
|
||||
.unwrap_or(config.disable_user_registration)
|
||||
}
|
||||
|
||||
fn oidc_registration_disabled(&self) -> bool {
|
||||
let config = core_config();
|
||||
config
|
||||
.disable_oidc_user_registration
|
||||
.unwrap_or(config.disable_user_registration)
|
||||
}
|
||||
|
||||
fn validate_username(
|
||||
&self,
|
||||
username: &str,
|
||||
@@ -348,6 +362,7 @@ impl AuthImpl for KomodoAuthImpl {
|
||||
additional_audiences: config
|
||||
.oidc_additional_audiences
|
||||
.clone(),
|
||||
auto_redirect: config.oidc_auto_redirect,
|
||||
}
|
||||
});
|
||||
Some(&OIDC_CONFIG)
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn launch_ec2_instance(
|
||||
port: _,
|
||||
use_https: _,
|
||||
git_providers: _,
|
||||
docker_registries: _,
|
||||
image_registries: _,
|
||||
secrets: _,
|
||||
} = config;
|
||||
let instance_type = handle_unknown_instance_type(
|
||||
|
||||
@@ -2,8 +2,9 @@ pub mod aws;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BuildCleanupData {
|
||||
/// Nothing to clean up
|
||||
Server,
|
||||
/// Store the builder id if it needs
|
||||
/// to be removed from builder_usage_cache.
|
||||
Server(Option<String>),
|
||||
/// Cleanup Periphery connection
|
||||
Url,
|
||||
/// Clean up AWS instance
|
||||
|
||||
+38
-4
@@ -235,6 +235,9 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
env.komodo_oidc_additional_audiences,
|
||||
)
|
||||
.unwrap_or(config.oidc_additional_audiences),
|
||||
oidc_auto_redirect: env
|
||||
.komodo_oidc_auto_redirect
|
||||
.unwrap_or(config.oidc_auto_redirect),
|
||||
google_oauth: NamedOauthConfig {
|
||||
enabled: env
|
||||
.komodo_google_oauth_enabled
|
||||
@@ -312,6 +315,21 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
session_allow_cross_site: env
|
||||
.komodo_session_allow_cross_site
|
||||
.unwrap_or(config.session_allow_cross_site),
|
||||
x_content_type_options: env
|
||||
.komodo_x_content_type_options
|
||||
.unwrap_or(config.x_content_type_options),
|
||||
x_frame_options: env
|
||||
.komodo_x_frame_options
|
||||
.unwrap_or(config.x_frame_options),
|
||||
x_xss_protection: env
|
||||
.komodo_x_xss_protection
|
||||
.unwrap_or(config.x_xss_protection),
|
||||
referrer_policy: env
|
||||
.komodo_referrer_policy
|
||||
.unwrap_or(config.referrer_policy),
|
||||
content_security_policy: env
|
||||
.komodo_content_security_policy
|
||||
.unwrap_or(config.content_security_policy),
|
||||
resource_poll_interval: env
|
||||
.komodo_resource_poll_interval
|
||||
.unwrap_or(config.resource_poll_interval),
|
||||
@@ -330,6 +348,9 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
transparent_mode: env
|
||||
.komodo_transparent_mode
|
||||
.unwrap_or(config.transparent_mode),
|
||||
default_pagination_limit: env
|
||||
.komodo_default_pagination_limit
|
||||
.unwrap_or(config.default_pagination_limit),
|
||||
ui_write_disabled: env
|
||||
.komodo_ui_write_disabled
|
||||
.unwrap_or(config.ui_write_disabled),
|
||||
@@ -345,6 +366,12 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
disable_user_registration: env
|
||||
.komodo_disable_user_registration
|
||||
.unwrap_or(config.disable_user_registration),
|
||||
disable_local_user_registration: env
|
||||
.komodo_disable_local_user_registration
|
||||
.or(config.disable_local_user_registration),
|
||||
disable_oidc_user_registration: env
|
||||
.komodo_disable_oidc_user_registration
|
||||
.or(config.disable_oidc_user_registration),
|
||||
disable_non_admin_create: env
|
||||
.komodo_disable_non_admin_create
|
||||
.unwrap_or(config.disable_non_admin_create),
|
||||
@@ -375,6 +402,9 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
.komodo_logging_location
|
||||
.unwrap_or(config.logging.location),
|
||||
ansi: env.komodo_logging_ansi.unwrap_or(config.logging.ansi),
|
||||
timestamps: env
|
||||
.komodo_logging_timestamps
|
||||
.unwrap_or(config.logging.timestamps),
|
||||
otlp_endpoint: env
|
||||
.komodo_logging_otlp_endpoint
|
||||
.unwrap_or(config.logging.otlp_endpoint),
|
||||
@@ -403,9 +433,13 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
ssl_cert_file: env
|
||||
.komodo_ssl_cert_file
|
||||
.unwrap_or(config.ssl_cert_file),
|
||||
ui_path: env
|
||||
.komodo_ui_path
|
||||
.unwrap_or(config.ui_path),
|
||||
reporting_enabled: env
|
||||
.komodo_reporting_enabled
|
||||
.unwrap_or(config.reporting_enabled),
|
||||
reporting_private_key: env
|
||||
.komodo_reporting_private_key
|
||||
.unwrap_or(config.reporting_private_key),
|
||||
ui_path: env.komodo_ui_path.unwrap_or(config.ui_path),
|
||||
ui_index_force_no_cache: env
|
||||
.komodo_ui_index_force_no_cache
|
||||
.unwrap_or(config.ui_index_force_no_cache),
|
||||
@@ -422,7 +456,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
// These can't be overridden on env
|
||||
secrets: config.secrets,
|
||||
git_providers: config.git_providers,
|
||||
docker_registries: config.docker_registries,
|
||||
image_registries: config.image_registries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use komodo_client::entities::server::Server;
|
||||
use periphery_client::{
|
||||
CONNECTION_RETRY_SECONDS, transport::LoginMessage,
|
||||
};
|
||||
@@ -18,6 +19,7 @@ use transport::{
|
||||
|
||||
use crate::{
|
||||
config::{core_config, core_connection_query},
|
||||
monitor::refresh_server_cache,
|
||||
periphery::PeripheryClient,
|
||||
state::periphery_connections,
|
||||
};
|
||||
@@ -46,6 +48,7 @@ impl PeripheryConnectionArgs<'_> {
|
||||
|
||||
let responses = connection.responses.clone();
|
||||
|
||||
let _id = id.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let ws = tokio::select! {
|
||||
@@ -94,6 +97,18 @@ impl PeripheryConnectionArgs<'_> {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Waits until after connection is handled then
|
||||
// force refreshes the server cache.
|
||||
let id = _id.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let Ok(server) = crate::resource::get::<Server>(&id).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
refresh_server_cache(&server, true).await;
|
||||
});
|
||||
|
||||
connection.handle_socket(socket, &mut receiver).await
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::str::FromStr;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use axum::{
|
||||
@@ -42,6 +42,7 @@ use crate::{
|
||||
api::write::WriteArgs,
|
||||
config::core_keys,
|
||||
helpers::query::id_or_name_filter,
|
||||
monitor::refresh_server_cache,
|
||||
resource::KomodoResource,
|
||||
state::{db_client, periphery_connections},
|
||||
};
|
||||
@@ -163,7 +164,7 @@ async fn existing_server_handler(
|
||||
socket: &mut socket,
|
||||
identifiers: identifiers.build(query.as_bytes()),
|
||||
private_key: core_keys().load().private.as_str(),
|
||||
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: true },
|
||||
public_key_validator: OnboardingKeyValidator { privileged_required: true },
|
||||
should_close: true
|
||||
})
|
||||
.await
|
||||
@@ -201,6 +202,13 @@ async fn existing_server_handler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Waits until after connection is handled then
|
||||
// force refreshes the server cache.
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
refresh_server_cache(&server, true).await;
|
||||
});
|
||||
|
||||
connection.handle_socket(socket, &mut receiver).await
|
||||
}))
|
||||
}
|
||||
@@ -227,7 +235,7 @@ async fn fix_existing_server_handler(
|
||||
socket: &mut socket,
|
||||
identifiers: identifiers.build(query.as_bytes()),
|
||||
private_key: core_keys().load().private.as_str(),
|
||||
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: true },
|
||||
public_key_validator: OnboardingKeyValidator { privileged_required: true },
|
||||
should_close: true,
|
||||
})
|
||||
.await
|
||||
@@ -299,6 +307,13 @@ async fn fix_server(
|
||||
} else {
|
||||
None
|
||||
},
|
||||
// Move address to external_address if not set.
|
||||
// This helps preserve container port link behavior.
|
||||
external_address: if !server.config.external_address.is_empty() {
|
||||
Some(server.config.address)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
if !config.is_none() {
|
||||
@@ -341,7 +356,7 @@ async fn onboard_new_server_handler(
|
||||
socket: &mut socket,
|
||||
identifiers: identifiers.build(query.as_bytes()),
|
||||
private_key: core_keys().load().private.as_str(),
|
||||
public_key_validator: OnboardingKeyValidator { fix_existing_servers_required: false },
|
||||
public_key_validator: OnboardingKeyValidator { privileged_required: false },
|
||||
should_close: true
|
||||
})
|
||||
.await
|
||||
@@ -488,7 +503,7 @@ async fn create_server_maybe_builder(
|
||||
name: server_query,
|
||||
config: PartialBuilderConfig::Server(
|
||||
PartialServerBuilderConfig {
|
||||
server_id: Some(server.id.clone()),
|
||||
server_ids: Some(vec![server.id.clone()]),
|
||||
},
|
||||
),
|
||||
})
|
||||
@@ -505,7 +520,7 @@ async fn create_server_maybe_builder(
|
||||
}
|
||||
|
||||
struct OnboardingKeyValidator {
|
||||
fix_existing_servers_required: bool,
|
||||
privileged_required: bool,
|
||||
}
|
||||
|
||||
impl PublicKeyValidator for OnboardingKeyValidator {
|
||||
@@ -527,9 +542,7 @@ impl PublicKeyValidator for OnboardingKeyValidator {
|
||||
{
|
||||
return Err(anyhow!("Onboarding key is invalid"));
|
||||
}
|
||||
if self.fix_existing_servers_required
|
||||
&& !onboarding_key.fix_existing_servers
|
||||
{
|
||||
if self.privileged_required && !onboarding_key.privileged {
|
||||
return Err(anyhow!(
|
||||
"Onboarding key not able to fix existing servers"
|
||||
));
|
||||
|
||||
+112
-27
@@ -1,16 +1,18 @@
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mungos::mongodb::bson::oid::ObjectId;
|
||||
use formatting::muted;
|
||||
use futures_util::{StreamExt, stream::FuturesOrdered};
|
||||
use komodo_client::entities::{
|
||||
Version,
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
komodo_timestamp,
|
||||
server::Server,
|
||||
server::{Server, ServerState},
|
||||
update::{Log, Update},
|
||||
};
|
||||
use periphery_client::api::{self, GetVersionResponse};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
cloud::{
|
||||
@@ -24,6 +26,7 @@ use crate::{
|
||||
helpers::update::update_update,
|
||||
periphery::PeripheryClient,
|
||||
resource,
|
||||
state::{builder_usage_cache, server_status_cache},
|
||||
};
|
||||
|
||||
use super::periphery_client;
|
||||
@@ -37,25 +40,26 @@ const BUILDER_POLL_MAX_TRIES: usize = 60;
|
||||
fields(
|
||||
resource_name,
|
||||
builder_id = builder.id,
|
||||
update_id = update.id
|
||||
update_id = update.as_ref().map(|u| u.id.as_str())
|
||||
)
|
||||
)]
|
||||
pub async fn connect_builder_periphery(
|
||||
// build: &Build,
|
||||
resource_name: String,
|
||||
version: Option<Version>,
|
||||
builder: Builder,
|
||||
update: &mut Update,
|
||||
update: Option<&mut Update>,
|
||||
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
|
||||
match builder.config {
|
||||
BuilderConfig::Aws(config) => {
|
||||
get_aws_builder(&resource_name, version, config, update).await
|
||||
}
|
||||
BuilderConfig::Url(config) => {
|
||||
if config.address.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Builder has not yet configured an address"
|
||||
));
|
||||
}
|
||||
// TODO: Dont use builder id, or will be problems
|
||||
// with simultaneous spawned builders.
|
||||
// Builder id no good because it may be active for multiple connections.
|
||||
let periphery = PeripheryClient::new(
|
||||
PeripheryConnectionArgs::from_url_builder(
|
||||
&ObjectId::new().to_hex(),
|
||||
@@ -64,22 +68,66 @@ pub async fn connect_builder_periphery(
|
||||
config.insecure_tls,
|
||||
)
|
||||
.await?;
|
||||
periphery
|
||||
.health_check()
|
||||
.await
|
||||
.context("Url Builder failed health check")?;
|
||||
Ok((periphery, BuildCleanupData::Url))
|
||||
// Poll for connection to be estalished
|
||||
let mut err = None;
|
||||
for _ in 0..10 {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
match periphery
|
||||
.health_check()
|
||||
.await
|
||||
.context("Url Builder failed health check")
|
||||
{
|
||||
Ok(_) => return Ok((periphery, BuildCleanupData::Url)),
|
||||
Err(e) => err = Some(e),
|
||||
};
|
||||
}
|
||||
Err(err.context("Missing error")?)
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_id.is_empty() {
|
||||
return Err(anyhow!("Builder has not configured a server"));
|
||||
if config.server_ids.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Server Builder has no configured Servers"
|
||||
));
|
||||
}
|
||||
let server = resource::get::<Server>(&config.server_id).await?;
|
||||
|
||||
// Short path for single configured builder
|
||||
if config.server_ids.len() == 1 {
|
||||
let server =
|
||||
resource::get::<Server>(&config.server_ids[0]).await?;
|
||||
let periphery = periphery_client(&server).await?;
|
||||
return Ok((periphery, BuildCleanupData::Server(None)));
|
||||
}
|
||||
|
||||
// Get filtered list of available Servers
|
||||
let server_status_cache = server_status_cache();
|
||||
let available_server_ids = config
|
||||
.server_ids
|
||||
.iter()
|
||||
.map(|server_id| async move {
|
||||
server_status_cache
|
||||
.get(server_id)
|
||||
.await
|
||||
.map(|s| matches!(s.state, ServerState::Ok))
|
||||
.unwrap_or_default()
|
||||
.then_some(server_id)
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let selected = builder_usage_cache()
|
||||
.get_or_insert_default(&builder.id)
|
||||
.await
|
||||
.select(&available_server_ids)
|
||||
.await
|
||||
.context("Server builder has no available servers")?;
|
||||
|
||||
let server = resource::get::<Server>(selected).await?;
|
||||
let periphery = periphery_client(&server).await?;
|
||||
Ok((periphery, BuildCleanupData::Server))
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
get_aws_builder(&resource_name, version, config, update).await
|
||||
Ok((periphery, BuildCleanupData::Server(Some(server.id))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,14 +137,14 @@ pub async fn connect_builder_periphery(
|
||||
skip_all,
|
||||
fields(
|
||||
resource_name,
|
||||
update_id = update.id,
|
||||
update_id = update.as_ref().map(|u| u.id.as_str()),
|
||||
)
|
||||
)]
|
||||
async fn get_aws_builder(
|
||||
resource_name: &str,
|
||||
version: Option<Version>,
|
||||
config: AwsBuilderConfig,
|
||||
update: &mut Update,
|
||||
mut update: Option<&mut Update>,
|
||||
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
|
||||
let start_create_ts = komodo_timestamp();
|
||||
|
||||
@@ -114,9 +162,10 @@ async fn get_aws_builder(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
if let Some(update) = &mut update {
|
||||
update.logs.push(log);
|
||||
update_update((*update).clone()).await?;
|
||||
}
|
||||
|
||||
let protocol = if config.use_https { "wss" } else { "ws" };
|
||||
|
||||
@@ -153,8 +202,10 @@ async fn get_aws_builder(
|
||||
end_ts: komodo_timestamp(),
|
||||
..Default::default()
|
||||
};
|
||||
update.logs.push(connect_log);
|
||||
update_update(update.clone()).await?;
|
||||
if let Some(update) = update {
|
||||
update.logs.push(connect_log);
|
||||
update_update(update.clone()).await?;
|
||||
}
|
||||
return Ok((
|
||||
periphery,
|
||||
BuildCleanupData::Aws {
|
||||
@@ -194,9 +245,17 @@ pub async fn cleanup_builder_instance(
|
||||
update: &mut Update,
|
||||
) {
|
||||
match cleanup_data {
|
||||
BuildCleanupData::Server => {
|
||||
BuildCleanupData::Server(None) => {
|
||||
// Nothing to clean up
|
||||
}
|
||||
BuildCleanupData::Server(Some(builder_id)) => {
|
||||
// Release periphery (server) id from builder
|
||||
builder_usage_cache()
|
||||
.get_or_insert_default(&builder_id)
|
||||
.await
|
||||
.release(&periphery.id)
|
||||
.await
|
||||
}
|
||||
BuildCleanupData::Url => {
|
||||
periphery.cleanup().await;
|
||||
}
|
||||
@@ -252,3 +311,29 @@ pub fn start_aws_builder_log(
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BuilderUsage(Mutex<HashMap<String, usize>>);
|
||||
|
||||
impl BuilderUsage {
|
||||
pub async fn select<'a>(
|
||||
&self,
|
||||
available: &'a [&String],
|
||||
) -> Option<&'a str> {
|
||||
let mut lock = self.0.lock().await;
|
||||
let selected = *available.iter().min_by_key(|key| {
|
||||
lock.get(key.as_str()).copied().unwrap_or(0)
|
||||
})?;
|
||||
*lock.entry(selected.clone()).or_insert(0) += 1;
|
||||
Some(selected.as_str())
|
||||
}
|
||||
|
||||
pub async fn release(&self, key: &str) {
|
||||
let mut lock = self.0.lock().await;
|
||||
if let Some(count) = lock.get_mut(key)
|
||||
&& *count > 0
|
||||
{
|
||||
*count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,34 +39,34 @@ impl ImageDigestCache {
|
||||
account: Option<String>,
|
||||
token: Option<String>,
|
||||
) -> anyhow::Result<ImageDigest> {
|
||||
if let Some((digest, valid_until)) = self.0.get(&image).await
|
||||
if let Some((digest, valid_until)) = self.0.get(image).await
|
||||
// Ensure the query time was within last 10 mins to use cache.
|
||||
&& valid_until > komodo_timestamp()
|
||||
{
|
||||
return Ok(digest);
|
||||
} else {
|
||||
let digest = swarm_or_server_request(
|
||||
&swarm_or_server,
|
||||
GetLatestImageDigest {
|
||||
name: image.clone(),
|
||||
account,
|
||||
token,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.digest;
|
||||
|
||||
let digest = ImageDigest::new(image, &digest);
|
||||
|
||||
self
|
||||
.0
|
||||
.insert(
|
||||
image,
|
||||
(digest.clone(), komodo_timestamp() + 10 * 60 * 1_000),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(digest)
|
||||
}
|
||||
|
||||
let digest = swarm_or_server_request(
|
||||
swarm_or_server,
|
||||
GetLatestImageDigest {
|
||||
name: image.clone(),
|
||||
account,
|
||||
token,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.digest;
|
||||
|
||||
let digest = ImageDigest::new(image, &digest);
|
||||
|
||||
self
|
||||
.0
|
||||
.insert(
|
||||
image,
|
||||
(digest.clone(), komodo_timestamp() + 10 * 60 * 1_000),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(digest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ pub async fn registry_token(
|
||||
}
|
||||
Ok(
|
||||
core_config()
|
||||
.docker_registries
|
||||
.image_registries
|
||||
.iter()
|
||||
.find(|provider| provider.domain == provider_domain)
|
||||
.and_then(|provider| {
|
||||
|
||||
+387
-1167
File diff suppressed because it is too large
Load Diff
@@ -55,7 +55,7 @@ async fn prune_images() -> anyhow::Result<()> {
|
||||
while let Some((res, server)) = futures.next().await {
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"failed to prune images on Server {} ({}) | {e:#}",
|
||||
"Failed to prune images on Server {} ({}) | {e:#}",
|
||||
server.name, server.id
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ use crate::{
|
||||
state::{
|
||||
action_state_cache, action_states, db_client,
|
||||
deployment_status_cache, procedure_state_cache,
|
||||
stack_status_cache,
|
||||
server_status_cache, stack_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -97,6 +97,38 @@ pub async fn get_server_state(server: &Server) -> ServerState {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Server state as computed for the server list items,
|
||||
/// from the in memory status cache.
|
||||
pub async fn get_cached_server_state(id: &String) -> ServerState {
|
||||
server_status_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.map(|status| status.state)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The Stack state as computed for the stack list items,
|
||||
/// from the in memory action states / status cache.
|
||||
pub async fn get_cached_stack_state(id: &String) -> StackState {
|
||||
if action_states()
|
||||
.stack
|
||||
.get(id)
|
||||
.await
|
||||
.map(|s| s.get().map(|s| s.deploying))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return StackState::Deploying;
|
||||
}
|
||||
stack_status_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.map(|status| status.curr.state)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn get_deployment_state(
|
||||
id: &String,
|
||||
) -> anyhow::Result<DeploymentState> {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use komodo_client::entities::server::{Server, ServerState};
|
||||
use mogh_resolver::HasResponse;
|
||||
@@ -13,23 +11,6 @@ pub async fn swarm_request<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
swarm_request_custom_timeout(
|
||||
server_ids,
|
||||
request,
|
||||
Duration::from_secs(10),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn swarm_request_custom_timeout<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
@@ -54,10 +35,7 @@ where
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match periphery
|
||||
.request_custom_timeout(request.clone(), timeout)
|
||||
.await
|
||||
{
|
||||
match periphery.request(request.clone()).await {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{ContainerTerminalMode, TerminalTarget},
|
||||
terminal::{ContainerTerminalMode, Terminal, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
@@ -84,7 +84,7 @@ async fn setup_server_target_for_user(
|
||||
if let Some(init) = init {
|
||||
periphery
|
||||
.request(api::terminal::CreateServerTerminal {
|
||||
name: terminal.clone(),
|
||||
name: Some(terminal.clone()),
|
||||
command: init.command,
|
||||
recreate: init.recreate,
|
||||
})
|
||||
@@ -131,7 +131,7 @@ async fn setup_container_target_for_user(
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
name: Some(terminal.clone()),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
@@ -166,7 +166,7 @@ async fn setup_stack_service_target_for_user(
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
name: Some(terminal.clone()),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
@@ -199,7 +199,7 @@ async fn setup_deployment_target_for_user(
|
||||
if let Some(init) = init {
|
||||
create_container_terminal_inner(
|
||||
CreateTerminal {
|
||||
name: terminal.clone(),
|
||||
name: Some(terminal.clone()),
|
||||
target: target.clone(),
|
||||
command: init.command,
|
||||
mode: init.mode,
|
||||
@@ -241,7 +241,7 @@ pub async fn create_container_terminal_inner(
|
||||
}: CreateTerminal,
|
||||
periphery: &PeripheryClient,
|
||||
container: String,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<Terminal> {
|
||||
match mode.unwrap_or_default() {
|
||||
ContainerTerminalMode::Exec => periphery
|
||||
.request(periphery_client::api::terminal::CreateContainerExecTerminal {
|
||||
@@ -254,7 +254,7 @@ pub async fn create_container_terminal_inner(
|
||||
.await
|
||||
.context(
|
||||
"Failed to create Container Exec Terminal on Periphery",
|
||||
)?,
|
||||
),
|
||||
ContainerTerminalMode::Attach => periphery
|
||||
.request(periphery_client::api::terminal::CreateContainerAttachTerminal {
|
||||
name,
|
||||
@@ -265,9 +265,8 @@ pub async fn create_container_terminal_inner(
|
||||
.await
|
||||
.context(
|
||||
"Failed to create Container Attach Terminal on Periphery",
|
||||
)?,
|
||||
};
|
||||
Ok(())
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_stack_service_periphery_container(
|
||||
@@ -333,7 +332,7 @@ pub async fn get_deployment_periphery_container(
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let container = deployment.name.clone();
|
||||
let container = deployment.deployed_name().to_string();
|
||||
|
||||
Ok((
|
||||
TerminalTarget::Deployment {
|
||||
|
||||
+146
-457
@@ -123,464 +123,153 @@ pub async fn init_execution_update(
|
||||
request: &ExecuteRequest,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Update> {
|
||||
let (operation, target) = match &request {
|
||||
// Swarm
|
||||
ExecuteRequest::RemoveSwarmNodes(data) => (
|
||||
Operation::RemoveSwarmNodes,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RemoveSwarmStacks(data) => (
|
||||
Operation::RemoveSwarmStacks,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RemoveSwarmServices(data) => (
|
||||
Operation::RemoveSwarmServices,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::CreateSwarmConfig(data) => (
|
||||
Operation::CreateSwarmConfig,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RotateSwarmConfig(data) => (
|
||||
Operation::RotateSwarmConfig,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RemoveSwarmConfigs(data) => (
|
||||
Operation::RemoveSwarmConfigs,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::CreateSwarmSecret(data) => (
|
||||
Operation::CreateSwarmSecret,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RotateSwarmSecret(data) => (
|
||||
Operation::RotateSwarmSecret,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RemoveSwarmSecrets(data) => (
|
||||
Operation::RemoveSwarmSecrets,
|
||||
ResourceTarget::Swarm(
|
||||
resource::get::<Swarm>(&data.swarm).await?.id,
|
||||
),
|
||||
),
|
||||
macro_rules! init_execution_match {
|
||||
(
|
||||
resource: [$(($Variant:ident, $ResType:ident, $field:ident)),* $(,)?],
|
||||
batch: [$($BatchVariant:ident),* $(,)?],
|
||||
stack_service: [$(($StackVariant:ident, $ServiceOp:ident)),* $(,)?],
|
||||
system: [$($SysVariant:ident),* $(,)?],
|
||||
) => {
|
||||
match &request {
|
||||
$(
|
||||
ExecuteRequest::$Variant(data) => (
|
||||
Operation::$Variant,
|
||||
ResourceTarget::$ResType(
|
||||
resource::get::<$ResType>(&data.$field).await?.id,
|
||||
),
|
||||
),
|
||||
)*
|
||||
$(
|
||||
ExecuteRequest::$BatchVariant(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
)*
|
||||
$(
|
||||
ExecuteRequest::$StackVariant(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::$ServiceOp
|
||||
} else {
|
||||
Operation::$StackVariant
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
)*
|
||||
// DeployStackIfChanged doesn't have a service variant
|
||||
ExecuteRequest::DeployStackIfChanged(data) => (
|
||||
Operation::DeployStack,
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
$(
|
||||
ExecuteRequest::$SysVariant(_data) => {
|
||||
(Operation::$SysVariant, ResourceTarget::system())
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Server
|
||||
ExecuteRequest::StartContainer(data) => (
|
||||
Operation::StartContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RestartContainer(data) => (
|
||||
Operation::RestartContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PauseContainer(data) => (
|
||||
Operation::PauseContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::UnpauseContainer(data) => (
|
||||
Operation::UnpauseContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StopContainer(data) => (
|
||||
Operation::StopContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DestroyContainer(data) => (
|
||||
Operation::DestroyContainer,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StartAllContainers(data) => (
|
||||
Operation::StartAllContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RestartAllContainers(data) => (
|
||||
Operation::RestartAllContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PauseAllContainers(data) => (
|
||||
Operation::PauseAllContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::UnpauseAllContainers(data) => (
|
||||
Operation::UnpauseAllContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StopAllContainers(data) => (
|
||||
Operation::StopAllContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneContainers(data) => (
|
||||
Operation::PruneContainers,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DeleteNetwork(data) => (
|
||||
Operation::DeleteNetwork,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneNetworks(data) => (
|
||||
Operation::PruneNetworks,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DeleteImage(data) => (
|
||||
Operation::DeleteImage,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneImages(data) => (
|
||||
Operation::PruneImages,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DeleteVolume(data) => (
|
||||
Operation::DeleteVolume,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneVolumes(data) => (
|
||||
Operation::PruneVolumes,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneDockerBuilders(data) => (
|
||||
Operation::PruneDockerBuilders,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneBuildx(data) => (
|
||||
Operation::PruneBuildx,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PruneSystem(data) => (
|
||||
Operation::PruneSystem,
|
||||
ResourceTarget::Server(
|
||||
resource::get::<Server>(&data.server).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Deployment
|
||||
ExecuteRequest::Deploy(data) => (
|
||||
Operation::Deploy,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchDeploy(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::PullDeployment(data) => (
|
||||
Operation::PullDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StartDeployment(data) => (
|
||||
Operation::StartDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RestartDeployment(data) => (
|
||||
Operation::RestartDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PauseDeployment(data) => (
|
||||
Operation::PauseDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::UnpauseDeployment(data) => (
|
||||
Operation::UnpauseDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StopDeployment(data) => (
|
||||
Operation::StopDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DestroyDeployment(data) => (
|
||||
Operation::DestroyDeployment,
|
||||
ResourceTarget::Deployment(
|
||||
resource::get::<Deployment>(&data.deployment).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchDestroyDeployment(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Build
|
||||
ExecuteRequest::RunBuild(data) => (
|
||||
Operation::RunBuild,
|
||||
ResourceTarget::Build(
|
||||
resource::get::<Build>(&data.build).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchRunBuild(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::CancelBuild(data) => (
|
||||
Operation::CancelBuild,
|
||||
ResourceTarget::Build(
|
||||
resource::get::<Build>(&data.build).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Repo
|
||||
ExecuteRequest::CloneRepo(data) => (
|
||||
Operation::CloneRepo,
|
||||
ResourceTarget::Repo(
|
||||
resource::get::<Repo>(&data.repo).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchCloneRepo(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::PullRepo(data) => (
|
||||
Operation::PullRepo,
|
||||
ResourceTarget::Repo(
|
||||
resource::get::<Repo>(&data.repo).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchPullRepo(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::BuildRepo(data) => (
|
||||
Operation::BuildRepo,
|
||||
ResourceTarget::Repo(
|
||||
resource::get::<Repo>(&data.repo).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchBuildRepo(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::CancelRepoBuild(data) => (
|
||||
Operation::CancelRepoBuild,
|
||||
ResourceTarget::Repo(
|
||||
resource::get::<Repo>(&data.repo).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Procedure
|
||||
ExecuteRequest::RunProcedure(data) => (
|
||||
Operation::RunProcedure,
|
||||
ResourceTarget::Procedure(
|
||||
resource::get::<Procedure>(&data.procedure).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchRunProcedure(_) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Action
|
||||
ExecuteRequest::RunAction(data) => (
|
||||
Operation::RunAction,
|
||||
ResourceTarget::Action(
|
||||
resource::get::<Action>(&data.action).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchRunAction(_) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// Resource Sync
|
||||
ExecuteRequest::RunSync(data) => (
|
||||
Operation::RunSync,
|
||||
ResourceTarget::ResourceSync(
|
||||
resource::get::<ResourceSync>(&data.sync).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Stack
|
||||
ExecuteRequest::DeployStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::DeployStackService
|
||||
} else {
|
||||
Operation::DeployStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchDeployStack(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::DeployStackIfChanged(data) => (
|
||||
Operation::DeployStack,
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchDeployStackIfChanged(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::StartStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::StartStackService
|
||||
} else {
|
||||
Operation::StartStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PullStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::PullStackService
|
||||
} else {
|
||||
Operation::PullStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchPullStack(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
ExecuteRequest::RestartStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::RestartStackService
|
||||
} else {
|
||||
Operation::RestartStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PauseStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::PauseStackService
|
||||
} else {
|
||||
Operation::PauseStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::UnpauseStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::UnpauseStackService
|
||||
} else {
|
||||
Operation::UnpauseStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StopStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::StopStackService
|
||||
} else {
|
||||
Operation::StopStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DestroyStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
Operation::DestroyStackService
|
||||
} else {
|
||||
Operation::DestroyStack
|
||||
},
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::BatchDestroyStack(_data) => {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
ExecuteRequest::RunStackService(data) => (
|
||||
Operation::RunStackService,
|
||||
ResourceTarget::Stack(
|
||||
resource::get::<Stack>(&data.stack).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Alerter
|
||||
ExecuteRequest::TestAlerter(data) => (
|
||||
Operation::TestAlerter,
|
||||
ResourceTarget::Alerter(
|
||||
resource::get::<Alerter>(&data.alerter).await?.id,
|
||||
),
|
||||
),
|
||||
ExecuteRequest::SendAlert(_) => {
|
||||
(Operation::SendAlert, ResourceTarget::system())
|
||||
}
|
||||
|
||||
// Maintenance
|
||||
ExecuteRequest::ClearRepoCache(_data) => {
|
||||
(Operation::ClearRepoCache, ResourceTarget::system())
|
||||
}
|
||||
ExecuteRequest::BackupCoreDatabase(_data) => {
|
||||
(Operation::BackupCoreDatabase, ResourceTarget::system())
|
||||
}
|
||||
ExecuteRequest::GlobalAutoUpdate(_data) => {
|
||||
(Operation::GlobalAutoUpdate, ResourceTarget::system())
|
||||
}
|
||||
ExecuteRequest::RotateAllServerKeys(_data) => {
|
||||
(Operation::RotateAllServerKeys, ResourceTarget::system())
|
||||
}
|
||||
ExecuteRequest::RotateCoreKeys(_data) => {
|
||||
(Operation::RotateCoreKeys, ResourceTarget::system())
|
||||
}
|
||||
};
|
||||
let (operation, target) = init_execution_match!(
|
||||
resource: [
|
||||
// Swarm
|
||||
(RemoveSwarmNodes, Swarm, swarm),
|
||||
(UpdateSwarmNode, Swarm, swarm),
|
||||
(RemoveSwarmStacks, Swarm, swarm),
|
||||
(RemoveSwarmServices, Swarm, swarm),
|
||||
(CreateSwarmConfig, Swarm, swarm),
|
||||
(RotateSwarmConfig, Swarm, swarm),
|
||||
(RemoveSwarmConfigs, Swarm, swarm),
|
||||
(CreateSwarmSecret, Swarm, swarm),
|
||||
(RotateSwarmSecret, Swarm, swarm),
|
||||
(RemoveSwarmSecrets, Swarm, swarm),
|
||||
// Server
|
||||
(StartContainer, Server, server),
|
||||
(RestartContainer, Server, server),
|
||||
(PauseContainer, Server, server),
|
||||
(UnpauseContainer, Server, server),
|
||||
(StopContainer, Server, server),
|
||||
(DestroyContainer, Server, server),
|
||||
(StartAllContainers, Server, server),
|
||||
(RestartAllContainers, Server, server),
|
||||
(PauseAllContainers, Server, server),
|
||||
(UnpauseAllContainers, Server, server),
|
||||
(StopAllContainers, Server, server),
|
||||
(PruneContainers, Server, server),
|
||||
(DeleteNetwork, Server, server),
|
||||
(PruneNetworks, Server, server),
|
||||
(DeleteImage, Server, server),
|
||||
(PruneImages, Server, server),
|
||||
(DeleteVolume, Server, server),
|
||||
(PruneVolumes, Server, server),
|
||||
(PruneDockerBuilders, Server, server),
|
||||
(PruneBuildx, Server, server),
|
||||
(PruneSystem, Server, server),
|
||||
// Deployment
|
||||
(Deploy, Deployment, deployment),
|
||||
(PullDeployment, Deployment, deployment),
|
||||
(StartDeployment, Deployment, deployment),
|
||||
(RestartDeployment, Deployment, deployment),
|
||||
(PauseDeployment, Deployment, deployment),
|
||||
(UnpauseDeployment, Deployment, deployment),
|
||||
(StopDeployment, Deployment, deployment),
|
||||
(DestroyDeployment, Deployment, deployment),
|
||||
// Build
|
||||
(RunBuild, Build, build),
|
||||
(CancelBuild, Build, build),
|
||||
// Repo
|
||||
(CloneRepo, Repo, repo),
|
||||
(PullRepo, Repo, repo),
|
||||
(BuildRepo, Repo, repo),
|
||||
(CancelRepoBuild, Repo, repo),
|
||||
// Procedure
|
||||
(RunProcedure, Procedure, procedure),
|
||||
(CancelProcedure, Procedure, procedure),
|
||||
// Action
|
||||
(RunAction, Action, action),
|
||||
(CancelAction, Action, action),
|
||||
// Resource Sync
|
||||
(RunSync, ResourceSync, sync),
|
||||
// Stack (simple)
|
||||
(RunStackService, Stack, stack),
|
||||
// Alerter
|
||||
(TestAlerter, Alerter, alerter),
|
||||
],
|
||||
batch: [
|
||||
BatchDeploy,
|
||||
BatchDestroyDeployment,
|
||||
BatchRunBuild,
|
||||
BatchCloneRepo,
|
||||
BatchPullRepo,
|
||||
BatchBuildRepo,
|
||||
BatchRunProcedure,
|
||||
BatchRunAction,
|
||||
BatchDeployStack,
|
||||
BatchDeployStackIfChanged,
|
||||
BatchPullStack,
|
||||
BatchDestroyStack,
|
||||
],
|
||||
stack_service: [
|
||||
(DeployStack, DeployStackService),
|
||||
(PullStack, PullStackService),
|
||||
(StartStack, StartStackService),
|
||||
(RestartStack, RestartStackService),
|
||||
(PauseStack, PauseStackService),
|
||||
(UnpauseStack, UnpauseStackService),
|
||||
(StopStack, StopStackService),
|
||||
(DestroyStack, DestroyStackService),
|
||||
],
|
||||
system: [
|
||||
SendAlert,
|
||||
ClearRepoCache,
|
||||
BackupCoreDatabase,
|
||||
GlobalAutoUpdate,
|
||||
RotateAllServerKeys,
|
||||
RotateCoreKeys,
|
||||
],
|
||||
);
|
||||
|
||||
let mut update = make_update(target, operation, user);
|
||||
update.in_progress();
|
||||
|
||||
@@ -19,6 +19,7 @@ mod monitor;
|
||||
mod network;
|
||||
mod periphery;
|
||||
mod permission;
|
||||
mod reporting;
|
||||
mod resource;
|
||||
mod schedule;
|
||||
mod stack;
|
||||
@@ -71,6 +72,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
resource::spawn_action_state_refresh_loop();
|
||||
schedule::spawn_schedule_executor();
|
||||
helpers::prune::spawn_prune_loop();
|
||||
reporting::spawn_reporting_loop();
|
||||
}
|
||||
.instrument(startup_span)
|
||||
.await;
|
||||
|
||||
@@ -3,7 +3,8 @@ use std::collections::HashMap;
|
||||
use komodo_client::entities::{
|
||||
ResourceTarget,
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
deployment::{Deployment, DeploymentState}, optional_string,
|
||||
deployment::{Deployment, DeploymentState},
|
||||
optional_string,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
alert::AlertDataVariant, permission::PermissionLevel, resource::ResourceQuery, server::Server, swarm::Swarm, user::system_user
|
||||
alert::AlertDataVariant, permission::PermissionLevel,
|
||||
resource::ResourceQuery, server::Server, swarm::Swarm,
|
||||
user::system_user,
|
||||
};
|
||||
|
||||
use crate::resource;
|
||||
@@ -34,6 +36,8 @@ async fn get_all_swarms_map()
|
||||
-> anyhow::Result<(HashMap<String, Swarm>, HashMap<String, String>)> {
|
||||
let swarms = resource::list_full_for_user::<Swarm>(
|
||||
ResourceQuery::default(),
|
||||
None,
|
||||
None,
|
||||
system_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -59,6 +63,8 @@ async fn get_all_servers_map()
|
||||
{
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
ResourceQuery::default(),
|
||||
None,
|
||||
None,
|
||||
system_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -51,7 +51,7 @@ pub async fn alert_servers(
|
||||
|
||||
let mut alerts_to_open = Vec::<(Alert, SendAlerts)>::new();
|
||||
let mut alerts_to_update = Vec::<(Alert, SendAlerts)>::new();
|
||||
let mut alert_ids_to_close = Vec::<(Alert, SendAlerts)>::new();
|
||||
let mut alerts_to_close = Vec::<(Alert, SendAlerts)>::new();
|
||||
|
||||
let buffer = alert_buffer();
|
||||
|
||||
@@ -66,15 +66,21 @@ pub async fn alert_servers(
|
||||
let in_maintenance =
|
||||
is_in_maintenance(&server.config.maintenance_windows, ts);
|
||||
|
||||
// ===================
|
||||
// SERVER HEALTH
|
||||
// ===================
|
||||
// ===============
|
||||
// SERVER HEALTH
|
||||
// ===============
|
||||
let health_alert = server_alerts.as_ref().and_then(|alerts| {
|
||||
alerts.get(&AlertDataVariant::ServerUnreachable)
|
||||
});
|
||||
match (server_status.state, health_alert) {
|
||||
(ServerState::NotOk, None) => {
|
||||
// Only open unreachable alert if not in maintenance and buffer is ready
|
||||
match (
|
||||
server_status.state,
|
||||
health_alert,
|
||||
server.config.send_unreachable_alerts,
|
||||
) {
|
||||
// Skip alerting if alerts disabled
|
||||
(ServerState::NotOk, None, false) => {}
|
||||
(ServerState::NotOk, None, true) => {
|
||||
// Only open unreachable alert if unreachable alerts enabled and not in maintenance and buffer is ready
|
||||
if !in_maintenance
|
||||
&& buffer.ready_to_open(
|
||||
server_status.id.clone(),
|
||||
@@ -99,19 +105,14 @@ pub async fn alert_servers(
|
||||
.push((alert, server.config.send_unreachable_alerts))
|
||||
}
|
||||
}
|
||||
(ServerState::NotOk, Some(alert)) => {
|
||||
(ServerState::NotOk, Some(alert), true) => {
|
||||
// update alert err
|
||||
let mut alert = alert.clone();
|
||||
let (id, name, region) = match alert.data {
|
||||
AlertData::ServerUnreachable {
|
||||
id, name, region, ..
|
||||
} => (id, name, region),
|
||||
data => {
|
||||
error!(
|
||||
"got incorrect alert data in ServerStatus handler. got {data:?}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
alert.data = AlertData::ServerUnreachable {
|
||||
id,
|
||||
@@ -124,23 +125,30 @@ pub async fn alert_servers(
|
||||
alerts_to_update.push((alert, false));
|
||||
}
|
||||
|
||||
// Close an open alert
|
||||
(ServerState::Ok | ServerState::Disabled, Some(alert)) => {
|
||||
alert_ids_to_close.push((
|
||||
(ServerState::NotOk, Some(alert), false) => {
|
||||
alerts_to_close.push((
|
||||
alert.clone(),
|
||||
server.config.send_unreachable_alerts,
|
||||
));
|
||||
}
|
||||
(ServerState::Ok | ServerState::Disabled, None) => buffer
|
||||
|
||||
// Close an open alert
|
||||
(ServerState::Ok | ServerState::Disabled, Some(alert), _) => {
|
||||
alerts_to_close.push((
|
||||
alert.clone(),
|
||||
server.config.send_unreachable_alerts,
|
||||
));
|
||||
}
|
||||
(ServerState::Ok | ServerState::Disabled, None, _) => buffer
|
||||
.reset(
|
||||
server_status.id.clone(),
|
||||
AlertDataVariant::ServerUnreachable,
|
||||
),
|
||||
}
|
||||
|
||||
// ===================
|
||||
// ========================
|
||||
// SERVER VERSION MISMATCH
|
||||
// ===================
|
||||
// ========================
|
||||
let core_version = env!("CARGO_PKG_VERSION");
|
||||
let mismatched_server_version =
|
||||
if server_status.state != ServerState::Ok {
|
||||
@@ -158,9 +166,14 @@ pub async fn alert_servers(
|
||||
alerts.get(&AlertDataVariant::ServerVersionMismatch)
|
||||
});
|
||||
|
||||
match (mismatched_server_version, version_alert) {
|
||||
(Some(version), None) => {
|
||||
// Only open version mismatch alert if not in maintenance and buffer is ready
|
||||
match (
|
||||
mismatched_server_version,
|
||||
version_alert,
|
||||
server.config.send_version_mismatch_alerts,
|
||||
) {
|
||||
(Some(_), None, false) => {}
|
||||
(Some(version), None, true) => {
|
||||
// Only open version mismatch alert if version mismatch alerts enabled and not in maintenance and buffer is ready
|
||||
if !in_maintenance
|
||||
&& buffer.ready_to_open(
|
||||
server_status.id.clone(),
|
||||
@@ -187,27 +200,47 @@ pub async fn alert_servers(
|
||||
.push((alert, server.config.send_version_mismatch_alerts))
|
||||
}
|
||||
}
|
||||
(Some(version), Some(alert)) => {
|
||||
// Update existing alert with current version info
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerVersionMismatch {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.config.region),
|
||||
server_version: version.clone(),
|
||||
core_version: core_version.to_string(),
|
||||
};
|
||||
// Don't send notification for updates
|
||||
alerts_to_update.push((alert, false));
|
||||
(Some(version), Some(alert), true) => {
|
||||
// Update existing alert with current version info if changed
|
||||
let (alert_server_version, alert_core_version) =
|
||||
match &alert.data {
|
||||
AlertData::ServerVersionMismatch {
|
||||
server_version,
|
||||
core_version,
|
||||
..
|
||||
} => (server_version, core_version),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if alert_server_version != version
|
||||
|| alert_core_version != core_version
|
||||
{
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerVersionMismatch {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.config.region),
|
||||
server_version: version.clone(),
|
||||
core_version: core_version.to_string(),
|
||||
};
|
||||
// Don't send notification for updates
|
||||
alerts_to_update.push((alert, false));
|
||||
}
|
||||
}
|
||||
(None, Some(alert)) => {
|
||||
// Version is now correct, close the alert
|
||||
alert_ids_to_close.push((
|
||||
(Some(_), Some(alert), false) => {
|
||||
alerts_to_close.push((
|
||||
alert.clone(),
|
||||
server.config.send_version_mismatch_alerts,
|
||||
));
|
||||
}
|
||||
(None, None) => {
|
||||
(None, Some(alert), _) => {
|
||||
// Version is now correct, close the alert
|
||||
alerts_to_close.push((
|
||||
alert.clone(),
|
||||
server.config.send_version_mismatch_alerts,
|
||||
));
|
||||
}
|
||||
(None, None, _) => {
|
||||
// Reset buffer state when no mismatch and no alert
|
||||
buffer.reset(
|
||||
server_status.id.clone(),
|
||||
@@ -227,10 +260,25 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerCpu))
|
||||
.cloned();
|
||||
match (health.cpu.level, cpu_alert, health.cpu.should_close_alert)
|
||||
{
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
|
||||
// Only open CPU alert if not in maintenance and buffer is ready
|
||||
match (
|
||||
health.cpu.level,
|
||||
cpu_alert,
|
||||
health.cpu.should_close_alert,
|
||||
server.config.send_cpu_alerts,
|
||||
) {
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
false,
|
||||
) => {}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// Only open CPU alert if cpu alerts enabled and not in maintenance and buffer is ready
|
||||
if !in_maintenance
|
||||
&& buffer.ready_to_open(
|
||||
server_status.id.clone(),
|
||||
@@ -262,6 +310,7 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// modify alert level only if it has increased and not in maintenance
|
||||
if !in_maintenance && alert.level < health.cpu.level {
|
||||
@@ -280,8 +329,15 @@ pub async fn alert_servers(
|
||||
.push((alert, server.config.send_cpu_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
let mut alert = alert.clone();
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(alert),
|
||||
_,
|
||||
false,
|
||||
) => {
|
||||
alerts_to_close.push((alert, server.config.send_cpu_alerts));
|
||||
}
|
||||
(SeverityLevel::Ok, Some(mut alert), true, _) => {
|
||||
alert.data = AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
@@ -292,10 +348,9 @@ pub async fn alert_servers(
|
||||
.map(|s| s.cpu_perc as f64)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close
|
||||
.push((alert, server.config.send_cpu_alerts))
|
||||
alerts_to_close.push((alert, server.config.send_cpu_alerts))
|
||||
}
|
||||
(SeverityLevel::Ok, _, _) => buffer
|
||||
(SeverityLevel::Ok, _, _, _) => buffer
|
||||
.reset(server_status.id.clone(), AlertDataVariant::ServerCpu),
|
||||
}
|
||||
|
||||
@@ -306,10 +361,25 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerMem))
|
||||
.cloned();
|
||||
match (health.mem.level, mem_alert, health.mem.should_close_alert)
|
||||
{
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
|
||||
// Only open memory alert if not in maintenance and buffer is ready
|
||||
match (
|
||||
health.mem.level,
|
||||
mem_alert,
|
||||
health.mem.should_close_alert,
|
||||
server.config.send_mem_alerts,
|
||||
) {
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
false,
|
||||
) => {}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// Only open memory alert if memory alerts enabled and not in maintenance and buffer is ready
|
||||
if !in_maintenance
|
||||
&& buffer.ready_to_open(
|
||||
server_status.id.clone(),
|
||||
@@ -346,6 +416,7 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// modify alert level only if it has increased and not in maintenance
|
||||
if !in_maintenance && alert.level < health.mem.level {
|
||||
@@ -369,7 +440,16 @@ pub async fn alert_servers(
|
||||
.push((alert, server.config.send_mem_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(alert),
|
||||
_,
|
||||
false,
|
||||
) => {
|
||||
alerts_to_close
|
||||
.push((alert.clone(), server.config.send_mem_alerts));
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert), true, _) => {
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
@@ -386,10 +466,9 @@ pub async fn alert_servers(
|
||||
.map(|s| s.mem_used_gb)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close
|
||||
.push((alert, server.config.send_mem_alerts))
|
||||
alerts_to_close.push((alert, server.config.send_mem_alerts))
|
||||
}
|
||||
(SeverityLevel::Ok, _, _) => buffer
|
||||
(SeverityLevel::Ok, _, _, _) => buffer
|
||||
.reset(server_status.id.clone(), AlertDataVariant::ServerMem),
|
||||
}
|
||||
|
||||
@@ -405,13 +484,25 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(path))
|
||||
.cloned();
|
||||
match (health.level, disk_alert, health.should_close_alert) {
|
||||
match (
|
||||
health.level,
|
||||
disk_alert,
|
||||
health.should_close_alert,
|
||||
server.config.send_disk_alerts,
|
||||
) {
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
false,
|
||||
) => {}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// Only open disk alert if not in maintenance and buffer is ready
|
||||
// Only open disk alert if disk alerts enabled and not in maintenance and buffer is ready
|
||||
if !in_maintenance
|
||||
&& buffer.ready_to_open(
|
||||
server_status.id.clone(),
|
||||
@@ -450,6 +541,7 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
true,
|
||||
) => {
|
||||
// modify alert level only if it has increased and not in maintenance
|
||||
if !in_maintenance && health.level < alert.level {
|
||||
@@ -470,8 +562,16 @@ pub async fn alert_servers(
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
let mut alert = alert.clone();
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(alert),
|
||||
_,
|
||||
false,
|
||||
) => {
|
||||
alerts_to_close
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
(SeverityLevel::Ok, Some(mut alert), true, _) => {
|
||||
let disk =
|
||||
server_status.system_stats.as_ref().and_then(|stats| {
|
||||
stats.disks.iter().find(|disk| disk.mount == *path)
|
||||
@@ -485,10 +585,10 @@ pub async fn alert_servers(
|
||||
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
|
||||
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
|
||||
};
|
||||
alert_ids_to_close
|
||||
alerts_to_close
|
||||
.push((alert, server.config.send_disk_alerts))
|
||||
}
|
||||
(SeverityLevel::Ok, _, _) => buffer.reset(
|
||||
(SeverityLevel::Ok, _, _, _) => buffer.reset(
|
||||
server_status.id.clone(),
|
||||
AlertDataVariant::ServerDisk,
|
||||
),
|
||||
@@ -501,7 +601,7 @@ pub async fn alert_servers(
|
||||
if !health.disks.contains_key(path) {
|
||||
let mut alert = alert.clone();
|
||||
alert.level = SeverityLevel::Ok;
|
||||
alert_ids_to_close
|
||||
alerts_to_close
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
}
|
||||
@@ -511,7 +611,7 @@ pub async fn alert_servers(
|
||||
tokio::join!(
|
||||
open_new_alerts(&alerts_to_open),
|
||||
update_alerts(&alerts_to_update),
|
||||
resolve_alerts(&alert_ids_to_close),
|
||||
resolve_alerts(&alerts_to_close),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user