mirror of
https://github.com/moghtech/komodo.git
synced 2026-08-08 05:48:25 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a246eaf35a |
Generated
+271
-360
File diff suppressed because it is too large
Load Diff
+34
-36
@@ -9,8 +9,9 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.3.0"
|
||||
version = "2.2.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"
|
||||
@@ -33,34 +34,34 @@ slack = { version = "2.0.0", package = "slack_client_rs", default-features = fal
|
||||
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.7.0"
|
||||
mogh_auth_server = "1.7.0"
|
||||
mogh_auth_client = "1.5.0"
|
||||
mogh_auth_server = "1.5.0"
|
||||
mogh_secret_file = "1.0.1"
|
||||
mogh_validations = "1.0.1"
|
||||
mogh_rate_limit = "1.0.1"
|
||||
partial_derive2 = "0.5.0"
|
||||
mongo_indexed = "2.1.0"
|
||||
mongo_indexed = "2.0.2"
|
||||
mogh_resolver = "1.0.0"
|
||||
mogh_config = "1.1.0"
|
||||
mogh_logger = "1.3.4"
|
||||
mogh_logger = "1.3.3"
|
||||
mogh_server = "1.5.0"
|
||||
toml_pretty = "2.0.0"
|
||||
mogh_cache = "1.1.2"
|
||||
mogh_pki = "1.1.3"
|
||||
mungos = "3.7.0"
|
||||
mungos = "3.2.2"
|
||||
svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.13.4", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.19", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.19", features = ["sync"] }
|
||||
reqwest = { version = "0.13.3", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
|
||||
tokio = { version = "1.52.2", features = ["full"] }
|
||||
tokio-util = { version = "0.7.18", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["sync"] }
|
||||
pin-project-lite = "0.2.17"
|
||||
futures-util = "0.3.33"
|
||||
arc-swap = "1.9.2"
|
||||
futures-util = "0.3.32"
|
||||
arc-swap = "1.9.0"
|
||||
|
||||
# SERVER
|
||||
tokio-tungstenite = { version = "0.30.0", features = ["rustls-tls-native-roots"] }
|
||||
tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-native-roots"] }
|
||||
axum = { version = "0.8.9", features = ["ws", "json", "macros"] }
|
||||
axum-extra = { version = "0.12.6", features = ["typed-header"] }
|
||||
|
||||
@@ -71,40 +72,37 @@ utoipa = "5.5.0"
|
||||
# SER/DE
|
||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde = { version = "1.0.227", features = ["derive"] }
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
bson = { version = "2.15.0" } # must keep in sync with mongodb version
|
||||
toml = "1.1.3"
|
||||
toml = "1.1.2"
|
||||
serde_yaml_ng = "0.10.0"
|
||||
serde_json = "1.0.151"
|
||||
serde_qs = "1.1.2"
|
||||
serde_json = "1.0.149"
|
||||
serde_qs = "1.1.0"
|
||||
url = "2.5.8"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.104"
|
||||
thiserror = "2.0.19"
|
||||
|
||||
# SYSTEM
|
||||
libc = "0.2.189"
|
||||
anyhow = "1.0.102"
|
||||
thiserror = "2.0.18"
|
||||
|
||||
# LOGGING
|
||||
tracing = "0.1.44"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.6.4", features = ["derive"] }
|
||||
clap = { version = "4.6.1", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
uuid = { version = "1.24.0", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.42", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.11.0"
|
||||
uuid = { version = "1.23.1", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.40", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.10.0"
|
||||
urlencoding = "2.1.3"
|
||||
bcrypt = "0.19.2"
|
||||
bcrypt = "0.19.0"
|
||||
hmac = "0.13.0"
|
||||
sha1 = "0.11.0"
|
||||
sha2 = "0.11.0"
|
||||
rand = "0.10.2"
|
||||
rand = "0.10.1"
|
||||
hex = "0.4.3"
|
||||
|
||||
# SYSTEM
|
||||
@@ -113,18 +111,18 @@ portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.21.0"
|
||||
sysinfo = "0.39.6"
|
||||
shlex = "2.0.1"
|
||||
sysinfo = "0.38.4"
|
||||
shlex = "1.3.0"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.10.0"
|
||||
aws-sdk-ec2 = "1.241.0"
|
||||
aws-credential-types = "1.3.0"
|
||||
aws-config = "1.8.16"
|
||||
aws-sdk-ec2 = "1.224.0"
|
||||
aws-credential-types = "1.2.14"
|
||||
|
||||
## CRON
|
||||
english-to-cron = "0.1.7"
|
||||
chrono-tz = "0.10.4"
|
||||
chrono = "0.4.45"
|
||||
chrono = "0.4.44"
|
||||
croner = "3.0.1"
|
||||
|
||||
# MISC
|
||||
@@ -135,8 +133,8 @@ comfy-table = "7.2.2"
|
||||
typeshare = "1.0.5"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.1.1"
|
||||
bytes = "1.12.1"
|
||||
regex = "1.13.1"
|
||||
bytes = "1.11.1"
|
||||
regex = "1.12.3"
|
||||
|
||||
[profile.release]
|
||||
strip = "debuginfo"
|
||||
@@ -1,4 +1,4 @@
|
||||
const cmd = "km run -y action deploy-komodo-ui-change";
|
||||
const cmd = "km run -y action deploy-komodo-fe-change";
|
||||
new Deno.Command("bash", {
|
||||
args: ["-c", cmd],
|
||||
}).spawn();
|
||||
@@ -1,7 +1,7 @@
|
||||
## Builds the Komodo Core, Periphery, and Util binaries
|
||||
## for a specific architecture. Requires OpenSSL 3 or later.
|
||||
|
||||
FROM rust:1.97.1-bookworm AS builder
|
||||
FROM rust:1.95.0-bookworm AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## Uses chef for dependency caching to help speed up back-to-back builds.
|
||||
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.97.1-bookworm AS chef
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.95.0-bookworm AS chef
|
||||
WORKDIR /builder
|
||||
|
||||
# Plan just the RECIPE to see if things have changed
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "komodo_cli"
|
||||
description = "Command line tool for Komodo"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.97.1-trixie AS builder
|
||||
FROM rust:1.95.0-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -7,16 +7,16 @@ use futures_util::{
|
||||
FutureExt, TryStreamExt, stream::FuturesUnordered,
|
||||
};
|
||||
use komodo_client::{
|
||||
api::read::{self, ListAllContainers, ListServers},
|
||||
api::read::{
|
||||
InspectDockerContainer, ListAllDockerContainers, ListServers,
|
||||
},
|
||||
entities::{
|
||||
config::cli::args::container::{
|
||||
Container, ContainerCommand, InspectContainer,
|
||||
},
|
||||
docker::{
|
||||
self,
|
||||
container::{
|
||||
ContainerListItem, ContainerSortBy, ContainerStateStatusEnum,
|
||||
},
|
||||
container::{ContainerListItem, ContainerStateStatusEnum},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -44,12 +44,11 @@ async fn list_containers(
|
||||
down,
|
||||
links,
|
||||
reverse,
|
||||
containers: terms,
|
||||
containers: names,
|
||||
images,
|
||||
networks,
|
||||
servers,
|
||||
format,
|
||||
page,
|
||||
command: _,
|
||||
}: &Container,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -61,16 +60,9 @@ async fn list_containers(
|
||||
.into_iter()
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllContainers {
|
||||
client.read(ListAllDockerContainers {
|
||||
servers: Default::default(),
|
||||
tags: Default::default(),
|
||||
terms: terms.clone(),
|
||||
state: Default::default(),
|
||||
limit: Some(100),
|
||||
// Page is more naturally given starting as 1, 2, 3.
|
||||
page: if *page == 0 { 0 } else { *page - 1 },
|
||||
sort_by: ContainerSortBy::Server,
|
||||
sort_desc: false,
|
||||
containers: Default::default(),
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -86,6 +78,7 @@ async fn list_containers(
|
||||
(Some(server.name.as_str()), c)
|
||||
});
|
||||
|
||||
let names = parse_wildcards(names);
|
||||
let servers = parse_wildcards(servers);
|
||||
let images = parse_wildcards(images);
|
||||
let networks = parse_wildcards(networks);
|
||||
@@ -112,6 +105,7 @@ async fn list_containers(
|
||||
);
|
||||
state_check
|
||||
&& network_check
|
||||
&& matches_wildcards(&names, &[c.name.as_str()])
|
||||
&& matches_wildcards(
|
||||
&servers,
|
||||
&server_name
|
||||
@@ -151,15 +145,9 @@ pub async fn inspect_container(
|
||||
.into_iter()
|
||||
.map(|s| (s.id.clone(), s))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
client.read(ListAllContainers {
|
||||
servers: inspect.servers.clone(),
|
||||
tags: Default::default(),
|
||||
terms: vec![inspect.container.clone()],
|
||||
state: Default::default(),
|
||||
limit: Some(0),
|
||||
page: 0,
|
||||
sort_by: ContainerSortBy::Server,
|
||||
sort_desc: false,
|
||||
client.read(ListAllDockerContainers {
|
||||
servers: Default::default(),
|
||||
containers: Default::default()
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -174,11 +162,25 @@ pub async fn inspect_container(
|
||||
c.server_id = Some(server.name.clone());
|
||||
});
|
||||
|
||||
let names = [inspect.container.to_string()];
|
||||
let names = parse_wildcards(&names);
|
||||
let servers = parse_wildcards(&inspect.servers);
|
||||
|
||||
let mut containers = containers
|
||||
.into_iter()
|
||||
.filter(|c| {
|
||||
matches_wildcards(&names, &[c.name.as_str()])
|
||||
&& matches_wildcards(
|
||||
&servers,
|
||||
&c.server_id
|
||||
.as_deref()
|
||||
.map(|i| vec![i])
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.map(|c| async move {
|
||||
client
|
||||
.read(read::InspectContainer {
|
||||
.read(InspectDockerContainer {
|
||||
container: c.name,
|
||||
server: c.server_id.context("No server...")?,
|
||||
})
|
||||
|
||||
@@ -81,9 +81,7 @@ pub async fn handle(
|
||||
let res = handle_execution!(
|
||||
execute: [
|
||||
RunAction,
|
||||
CancelAction,
|
||||
RunProcedure,
|
||||
CancelProcedure,
|
||||
RunBuild,
|
||||
CancelBuild,
|
||||
Deploy,
|
||||
|
||||
+31
-193
@@ -7,8 +7,7 @@ use komodo_client::{
|
||||
api::read::{
|
||||
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
||||
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
||||
ListSchedules, ListServers, ListStacks, ListSwarms, ListTags,
|
||||
ListTerminals,
|
||||
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
|
||||
},
|
||||
entities::{
|
||||
ResourceTargetVariant,
|
||||
@@ -32,7 +31,6 @@ use komodo_client::{
|
||||
schedule::Schedule,
|
||||
server::{ServerListItem, ServerListItemInfo, ServerState},
|
||||
stack::{StackListItem, StackListItemInfo, StackState},
|
||||
swarm::{SwarmListItem, SwarmListItemInfo, SwarmState},
|
||||
sync::{
|
||||
ResourceSyncListItem, ResourceSyncListItemInfo,
|
||||
ResourceSyncState,
|
||||
@@ -54,46 +52,28 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
||||
match &list.command {
|
||||
None => list_all(list).await,
|
||||
Some(ListCommand::Servers(filters)) => {
|
||||
list_resources::<ServerListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Swarms(filters)) => {
|
||||
list_resources::<SwarmListItem>(filters, list.page, false).await
|
||||
list_resources::<ServerListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Stacks(filters)) => {
|
||||
list_resources::<StackListItem>(filters, list.page, false).await
|
||||
list_resources::<StackListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Deployments(filters)) => {
|
||||
list_resources::<DeploymentListItem>(filters, list.page, false)
|
||||
.await
|
||||
list_resources::<DeploymentListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Builds(filters)) => {
|
||||
list_resources::<BuildListItem>(filters, list.page, false).await
|
||||
list_resources::<BuildListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Repos(filters)) => {
|
||||
list_resources::<RepoListItem>(filters, list.page, false).await
|
||||
list_resources::<RepoListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Procedures(filters)) => {
|
||||
list_resources::<ProcedureListItem>(filters, list.page, false)
|
||||
.await
|
||||
list_resources::<ProcedureListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Actions(filters)) => {
|
||||
list_resources::<ActionListItem>(filters, list.page, false)
|
||||
.await
|
||||
list_resources::<ActionListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Syncs(filters)) => {
|
||||
list_resources::<ResourceSyncListItem>(
|
||||
filters, list.page, false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Builders(filters)) => {
|
||||
list_resources::<BuilderListItem>(filters, list.page, false)
|
||||
.await
|
||||
}
|
||||
Some(ListCommand::Alerters(filters)) => {
|
||||
list_resources::<AlerterListItem>(filters, list.page, false)
|
||||
.await
|
||||
list_resources::<ResourceSyncListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Terminals(filters)) => {
|
||||
list_terminals(filters).await
|
||||
@@ -101,6 +81,12 @@ pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
||||
Some(ListCommand::Schedules(filters)) => {
|
||||
list_schedules(filters).await
|
||||
}
|
||||
Some(ListCommand::Builders(filters)) => {
|
||||
list_resources::<BuilderListItem>(filters, false).await
|
||||
}
|
||||
Some(ListCommand::Alerters(filters)) => {
|
||||
list_resources::<AlerterListItem>(filters, false).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +97,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
let (
|
||||
tags,
|
||||
mut servers,
|
||||
mut swarms,
|
||||
mut stacks,
|
||||
mut deployments,
|
||||
mut builds,
|
||||
@@ -124,15 +109,14 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>())),
|
||||
ServerListItem::list(client, &filters, list.page, true),
|
||||
SwarmListItem::list(client, &filters, list.page, true),
|
||||
StackListItem::list(client, &filters, list.page, true),
|
||||
DeploymentListItem::list(client, &filters, list.page, true),
|
||||
BuildListItem::list(client, &filters, list.page, true),
|
||||
RepoListItem::list(client, &filters, list.page, true),
|
||||
ProcedureListItem::list(client, &filters, list.page, true),
|
||||
ActionListItem::list(client, &filters, list.page, true),
|
||||
ResourceSyncListItem::list(client, &filters, list.page, true),
|
||||
ServerListItem::list(client, &filters, true),
|
||||
StackListItem::list(client, &filters, true),
|
||||
DeploymentListItem::list(client, &filters, true),
|
||||
BuildListItem::list(client, &filters, true),
|
||||
RepoListItem::list(client, &filters, true),
|
||||
ProcedureListItem::list(client, &filters, true),
|
||||
ActionListItem::list(client, &filters, true),
|
||||
ResourceSyncListItem::list(client, &filters, true),
|
||||
)?;
|
||||
|
||||
if !servers.is_empty() {
|
||||
@@ -141,12 +125,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
println!();
|
||||
}
|
||||
|
||||
if !swarms.is_empty() {
|
||||
fix_tags(&mut swarms, &tags);
|
||||
print_items(swarms, filters.format, list.links)?;
|
||||
println!();
|
||||
}
|
||||
|
||||
if !stacks.is_empty() {
|
||||
fix_tags(&mut stacks, &tags);
|
||||
print_items(stacks, filters.format, list.links)?;
|
||||
@@ -194,7 +172,6 @@ async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
||||
|
||||
async fn list_resources<T>(
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
@@ -203,7 +180,7 @@ where
|
||||
{
|
||||
let client = crate::command::komodo_client().await?;
|
||||
let (mut resources, tags) = tokio::try_join!(
|
||||
T::list(client, filters, page, minimal),
|
||||
T::list(client, filters, minimal),
|
||||
client.read(ListTags::default()).map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
@@ -228,8 +205,6 @@ async fn list_terminals(
|
||||
.read(ListTerminals {
|
||||
target: None,
|
||||
use_names: true,
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
if !terminals.is_empty() {
|
||||
@@ -247,8 +222,6 @@ async fn list_schedules(
|
||||
.read(ListSchedules {
|
||||
tags: filters.tags.clone(),
|
||||
tag_behavior: Default::default(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -306,7 +279,6 @@ where
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
// For use with root `km ls`
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
|
||||
@@ -319,7 +291,6 @@ impl ListResources for ServerListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let servers = client
|
||||
@@ -329,10 +300,6 @@ impl ListResources for ServerListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?;
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -362,68 +329,17 @@ impl ListResources for ServerListItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ListResources for SwarmListItem {
|
||||
type Info = SwarmListItemInfo;
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let servers = client
|
||||
.read(ListSwarms {
|
||||
query: ResourceQuery::builder()
|
||||
.tags(filters.tags.clone())
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?;
|
||||
let names = parse_wildcards(&filters.names);
|
||||
let server_wildcards = parse_wildcards(&filters.servers);
|
||||
let mut servers = servers
|
||||
.into_iter()
|
||||
.filter(|server| {
|
||||
let state_check = if filters.all {
|
||||
true
|
||||
} else if filters.down {
|
||||
!matches!(server.info.state, SwarmState::Healthy)
|
||||
} else if filters.in_progress {
|
||||
false
|
||||
} else {
|
||||
matches!(server.info.state, SwarmState::Healthy)
|
||||
};
|
||||
let name_items = &[server.name.as_str()];
|
||||
state_check
|
||||
&& matches_wildcards(&names, name_items)
|
||||
&& matches_wildcards(&server_wildcards, name_items)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
servers.sort_by(|a, b| {
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ListResources for StackListItem {
|
||||
type Info = StackListItemInfo;
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (servers, mut stacks) = tokio::try_join!(
|
||||
client
|
||||
.read(ListServers {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -435,10 +351,6 @@ impl ListResources for StackListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
stacks.iter_mut().for_each(|stack| {
|
||||
@@ -494,15 +406,12 @@ impl ListResources for DeploymentListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
_minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (servers, mut deployments) = tokio::try_join!(
|
||||
client
|
||||
.read(ListServers {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -514,10 +423,6 @@ impl ListResources for DeploymentListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
deployments.iter_mut().for_each(|deployment| {
|
||||
@@ -574,15 +479,12 @@ impl ListResources for BuildListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let (builders, mut builds) = tokio::try_join!(
|
||||
client
|
||||
.read(ListBuilders {
|
||||
query: ResourceQuery::builder().build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.map(|res| res.map(|res| res
|
||||
.into_iter()
|
||||
@@ -594,10 +496,6 @@ impl ListResources for BuildListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false
|
||||
})
|
||||
)?;
|
||||
builds.iter_mut().for_each(|build| {
|
||||
@@ -635,7 +533,10 @@ impl ListResources for BuildListItem {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
builds.sort_by(|a, b| {
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
a.name
|
||||
.cmp(&b.name)
|
||||
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
||||
.then(a.info.state.cmp(&b.info.state))
|
||||
});
|
||||
Ok(builds)
|
||||
}
|
||||
@@ -646,7 +547,6 @@ impl ListResources for RepoListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -657,10 +557,6 @@ impl ListResources for RepoListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -699,7 +595,6 @@ impl ListResources for ProcedureListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -710,10 +605,6 @@ impl ListResources for ProcedureListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -741,7 +632,7 @@ impl ListResources for ProcedureListItem {
|
||||
(Some(a), Some(b)) => return a.cmp(&b),
|
||||
(None, None) => {}
|
||||
}
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
});
|
||||
Ok(procedures)
|
||||
}
|
||||
@@ -752,7 +643,6 @@ impl ListResources for ActionListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -763,10 +653,6 @@ impl ListResources for ActionListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -794,7 +680,7 @@ impl ListResources for ActionListItem {
|
||||
(Some(a), Some(b)) => return a.cmp(&b),
|
||||
(None, None) => {}
|
||||
}
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
});
|
||||
Ok(actions)
|
||||
}
|
||||
@@ -805,7 +691,6 @@ impl ListResources for ResourceSyncListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -816,10 +701,6 @@ impl ListResources for ResourceSyncListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -844,7 +725,7 @@ impl ListResources for ResourceSyncListItem {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
syncs.sort_by(|a, b| {
|
||||
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
||||
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
||||
});
|
||||
Ok(syncs)
|
||||
}
|
||||
@@ -855,7 +736,6 @@ impl ListResources for BuilderListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -866,10 +746,6 @@ impl ListResources for BuilderListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -892,7 +768,6 @@ impl ListResources for AlerterListItem {
|
||||
async fn list(
|
||||
client: &KomodoClient,
|
||||
filters: &ResourceFilters,
|
||||
page: u64,
|
||||
minimal: bool,
|
||||
) -> anyhow::Result<Vec<Self>> {
|
||||
let names = parse_wildcards(&filters.names);
|
||||
@@ -903,10 +778,6 @@ impl ListResources for AlerterListItem {
|
||||
// .tag_behavior(TagQueryBehavior::Any)
|
||||
.templates(filters.templates)
|
||||
.build(),
|
||||
limit: None,
|
||||
page: page.saturating_sub(1),
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -961,39 +832,6 @@ impl PrintTable for ResourceListItem<ServerListItemInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for ResourceListItem<SwarmListItemInfo> {
|
||||
fn header(links: bool) -> &'static [&'static str] {
|
||||
if links {
|
||||
&["Swarm", "State", "Tags", "Link"]
|
||||
} else {
|
||||
&["Swarm", "State", "Tags"]
|
||||
}
|
||||
}
|
||||
fn row(self, links: bool) -> Vec<Cell> {
|
||||
let color = match self.info.state {
|
||||
SwarmState::Healthy => Color::Green,
|
||||
SwarmState::Unhealthy => Color::Red,
|
||||
SwarmState::Down => Color::Blue,
|
||||
SwarmState::Unknown => Color::Magenta,
|
||||
};
|
||||
let mut res = vec![
|
||||
Cell::new(self.name).add_attribute(Attribute::Bold),
|
||||
Cell::new(self.info.state.to_string())
|
||||
.fg(color)
|
||||
.add_attribute(Attribute::Bold),
|
||||
Cell::new(self.tags.join(", ")),
|
||||
];
|
||||
if links {
|
||||
res.push(Cell::new(resource_link(
|
||||
&cli_config().host,
|
||||
ResourceTargetVariant::Server,
|
||||
&self.id,
|
||||
)))
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for ResourceListItem<StackListItemInfo> {
|
||||
fn header(links: bool) -> &'static [&'static str] {
|
||||
if links {
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::{Context, anyhow};
|
||||
use colored::Colorize;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
read::{GetServer, ListAllContainers, ListServers},
|
||||
read::{GetServer, ListAllDockerContainers, ListServers},
|
||||
terminal::InitTerminal,
|
||||
},
|
||||
entities::{
|
||||
@@ -118,8 +118,6 @@ async fn get_server(
|
||||
server: Option<String>,
|
||||
container: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
// Don't need to find server for terminal,
|
||||
// user provided it, early return.
|
||||
if let Some(server) = server {
|
||||
return Ok(server);
|
||||
}
|
||||
@@ -127,15 +125,9 @@ async fn get_server(
|
||||
let client = super::komodo_client().await?;
|
||||
|
||||
let mut containers = client
|
||||
.read(ListAllContainers {
|
||||
.read(ListAllDockerContainers {
|
||||
servers: Default::default(),
|
||||
tags: Default::default(),
|
||||
terms: vec![container.to_string()],
|
||||
state: Default::default(),
|
||||
limit: Some(0),
|
||||
page: 0,
|
||||
sort_by: Default::default(),
|
||||
sort_desc: false,
|
||||
containers: vec![container.to_string()],
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -164,8 +156,6 @@ async fn get_server(
|
||||
let servers = client
|
||||
.read(ListServers {
|
||||
query: ServerQuery::builder().names(servers).build(),
|
||||
limit: Some(0),
|
||||
..Default::default()
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "komodo_core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.97.1-trixie AS core-builder
|
||||
FROM rust:1.95.0-trixie AS core-builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -4,26 +4,23 @@ use std::{
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use command::{CommandOptions, run_komodo_standard_command};
|
||||
use database::{
|
||||
bson::doc,
|
||||
mungos::{by_id::update_one_by_id, mongodb::bson::to_document},
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_standard_command;
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::bson::to_document,
|
||||
};
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
BatchExecutionResponse, BatchRunAction, CancelAction, RunAction,
|
||||
},
|
||||
api::execute::{BatchExecutionResponse, BatchRunAction, RunAction},
|
||||
entities::{
|
||||
FileFormat, JsonObject, Operation,
|
||||
FileFormat, JsonObject,
|
||||
action::Action,
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
config::core::CoreConfig,
|
||||
komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
random_string,
|
||||
update::{Update, UpdateStatus},
|
||||
update::Update,
|
||||
user::action_user,
|
||||
},
|
||||
parsers::parse_key_value_list,
|
||||
@@ -37,7 +34,6 @@ use mogh_auth_server::api::manage::api_key::{
|
||||
use mogh_config::merge_objects;
|
||||
use mogh_resolver::Resolve;
|
||||
use tokio::fs;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
alert::send_alerts,
|
||||
@@ -50,7 +46,7 @@ use crate::{
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource::refresh_action_state_cache,
|
||||
state::{action_cancel_cache, action_states, db_client},
|
||||
state::{action_states, db_client},
|
||||
};
|
||||
|
||||
use super::ExecuteArgs;
|
||||
@@ -73,7 +69,6 @@ impl Resolve<ExecuteArgs> for BatchRunAction {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -81,12 +76,8 @@ impl Resolve<ExecuteArgs> for BatchRunAction {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunAction>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunAction>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +116,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure action not already busy before updating.
|
||||
let action_guard = action_state.update_custom(
|
||||
let _action_guard = action_state.update_custom(
|
||||
|state| state.running += 1,
|
||||
|state| state.running -= 1,
|
||||
false,
|
||||
@@ -206,20 +197,14 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
""
|
||||
};
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
action_cancel_cache()
|
||||
.insert(update.id.clone(), cancel.clone())
|
||||
.await;
|
||||
|
||||
let mut res = run_komodo_standard_command(
|
||||
// Keep this stage name as is, the UI will find the latest update log by matching the stage name
|
||||
"Execute Action",
|
||||
None,
|
||||
format!(
|
||||
"deno run --allow-all{https_cert_flag}{reload} {}",
|
||||
path.display()
|
||||
),
|
||||
CommandOptions::default().cancel(cancel),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -233,7 +218,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
update.logs.push(res);
|
||||
update.finalize();
|
||||
|
||||
mogh_error::Ok(())
|
||||
mogh_error::Ok(update)
|
||||
}
|
||||
.await;
|
||||
|
||||
@@ -246,13 +231,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
);
|
||||
};
|
||||
|
||||
action_cancel_cache().remove(&update.id).await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
res?;
|
||||
let update = res?;
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with update_update.
|
||||
@@ -480,65 +459,3 @@ fn parse_action_arguments(
|
||||
.context("Failed to parse Json to action args"),
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ExecuteArgs> for CancelAction {
|
||||
#[instrument(
|
||||
"CancelAction",
|
||||
skip_all,
|
||||
fields(
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
update_id = update.id,
|
||||
action = self.action,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let action = get_check_permissions::<Action>(
|
||||
&self.action,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let update_id = if let Some(update_id) = self.update_id
|
||||
&& !update_id.is_empty()
|
||||
{
|
||||
update_id
|
||||
} else {
|
||||
db_client()
|
||||
.updates
|
||||
.find_one(doc! {
|
||||
"target.type": "Action",
|
||||
"target.id": &action.id,
|
||||
"operation": Operation::RunAction.as_ref(),
|
||||
"status": UpdateStatus::InProgress.as_ref(),
|
||||
})
|
||||
.await?
|
||||
.context("No active run to cancel found")?
|
||||
.id
|
||||
};
|
||||
|
||||
action_cancel_cache()
|
||||
.get(&update_id)
|
||||
.await
|
||||
.context("Action run cancel token not found")?
|
||||
.cancel();
|
||||
|
||||
let mut update = update.clone();
|
||||
update.push_simple_log(
|
||||
"Cancel Triggered",
|
||||
"The action cancel has been triggered.",
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,6 @@ impl Resolve<ExecuteArgs> for SendAlert {
|
||||
) -> 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,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::DeploymentState,
|
||||
komodo_timestamp, optional_string,
|
||||
permission::PermissionLevel,
|
||||
@@ -78,7 +78,6 @@ impl Resolve<ExecuteArgs> for BatchRunBuild {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -86,12 +85,8 @@ impl Resolve<ExecuteArgs> for BatchRunBuild {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunBuild>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunBuild>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -153,7 +148,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure build not already busy before updating.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
if build.config.auto_increment_version {
|
||||
@@ -180,6 +175,9 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
let builder =
|
||||
resource::get::<Builder>(&build.config.builder_id).await?;
|
||||
|
||||
let is_server_builder =
|
||||
matches!(&builder.config, BuilderConfig::Server(_));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let poll = async {
|
||||
loop {
|
||||
@@ -188,13 +186,19 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
id = cancel_recv.recv() => id?
|
||||
};
|
||||
if incoming_build_id == build_id {
|
||||
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
|
||||
if is_server_builder {
|
||||
update.push_error_log("Cancel acknowledged", "Build cancellation is not possible on server builders at this time. Use an AWS builder to enable this feature.");
|
||||
} else {
|
||||
update.push_simple_log("Cancel acknowledged", "The build cancellation has been queued, it may still take some time.");
|
||||
}
|
||||
update.finalize();
|
||||
let id = update.id.clone();
|
||||
if let Err(e) = update_update(update).await {
|
||||
warn!("Failed to modify Update {id} on db | {e:#}");
|
||||
}
|
||||
cancel_clone.cancel();
|
||||
if !is_server_builder {
|
||||
cancel_clone.cancel();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -212,7 +216,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
build.name.clone(),
|
||||
Some(build.config.version),
|
||||
builder,
|
||||
Some(&mut update),
|
||||
&mut update,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -269,11 +273,11 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
replacers: Default::default(),
|
||||
}) => res,
|
||||
_ = cancel.cancelled() => {
|
||||
debug!("Build cancelled during repo clone, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("Build cancelled during repo clone"));
|
||||
debug!("Build cancelled during clone, cleaning up builder");
|
||||
update.push_error_log("Build cancelled", String::from("user cancelled build during repo clone"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
debug!("Builder cleaned up");
|
||||
info!("Builder cleaned up");
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
@@ -319,14 +323,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
}) => res.context("Failed at call to Periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
info!("Build cancelled during build, cleaning up builder");
|
||||
if let Err(e) = periphery.request(api::build::CancelBuild {
|
||||
id: build.id.clone()
|
||||
})
|
||||
.await
|
||||
.context("Failed to cancel build execution on Server") {
|
||||
update.push_error_log("Cancel Build", format_serror(&e.into()));
|
||||
}
|
||||
update.push_error_log("Build Cancelled", String::from("User cancelled build during image build step"));
|
||||
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
@@ -376,10 +373,6 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
@@ -568,8 +561,8 @@ impl Resolve<ExecuteArgs> for CancelBuild {
|
||||
let mut update = update.clone();
|
||||
|
||||
update.push_simple_log(
|
||||
"Cancel Triggered",
|
||||
"The build cancel has been triggered",
|
||||
"cancel triggered",
|
||||
"the build cancel has been triggered",
|
||||
);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
@@ -624,13 +617,7 @@ async fn handle_post_build_redeploy(build_id: &str) {
|
||||
let state = get_deployment_state(&deployment.id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if ![
|
||||
DeploymentState::NotDeployed,
|
||||
DeploymentState::Exited,
|
||||
DeploymentState::Unknown,
|
||||
]
|
||||
.contains(&state)
|
||||
{
|
||||
if state == DeploymentState::Running {
|
||||
let req = super::ExecuteRequest::Deploy(Deploy {
|
||||
deployment: deployment.id.clone(),
|
||||
stop_signal: None,
|
||||
|
||||
@@ -58,7 +58,6 @@ impl Resolve<ExecuteArgs> for BatchDeploy {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -66,12 +65,8 @@ impl Resolve<ExecuteArgs> for BatchDeploy {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeploy>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchDeploy>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -115,7 +110,7 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -127,16 +122,26 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
let (version, registry_token) = match &deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(build_id).await?;
|
||||
let image_names = build.get_image_names();
|
||||
let image_name = image_names
|
||||
.first()
|
||||
.context("No image name could be created")
|
||||
.context("Failed to create image name")?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
*version
|
||||
};
|
||||
let image_name = build.get_deployment_image_name();
|
||||
let image_tag = build.get_deployment_image_tag(version);
|
||||
let version_str = version.to_string();
|
||||
// Potentially add the build image_tag postfix
|
||||
let version_str = if build.config.image_tag.is_empty() {
|
||||
version_str
|
||||
} else {
|
||||
format!("{version_str}-{}", build.config.image_tag)
|
||||
};
|
||||
// replace image with corresponding build image.
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{image_tag}"),
|
||||
image: format!("{image_name}:{version_str}"),
|
||||
};
|
||||
let first_registry = build
|
||||
.config
|
||||
@@ -206,12 +211,6 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let deployment_id = deployment.id.clone();
|
||||
// Track the name the container / service is deployed under,
|
||||
// so the Deployment stays matched to it even if the
|
||||
// name configuration changes before the next deploy.
|
||||
let fresh_name = deployment.custom_name().to_string();
|
||||
let prev_deployed_name = deployment.info.deployed_name.clone();
|
||||
let mut deployed = false;
|
||||
|
||||
match swarm_or_server {
|
||||
SwarmOrServer::None => unreachable!(),
|
||||
@@ -228,7 +227,6 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
{
|
||||
Ok(logs) => {
|
||||
refresh_swarm_cache(&swarm, true).await;
|
||||
deployed = logs.iter().all(|log| log.success);
|
||||
update.logs.extend(logs)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -253,7 +251,6 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
{
|
||||
Ok(log) => {
|
||||
refresh_server_cache(&server, true).await;
|
||||
deployed = log.success;
|
||||
update.logs.push(log)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -270,11 +267,6 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
&deployment_id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: Default::default(),
|
||||
deployed_name: if deployed {
|
||||
fresh_name
|
||||
} else {
|
||||
prev_deployed_name
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -286,11 +278,6 @@ impl Resolve<ExecuteArgs> for Deploy {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -471,7 +458,7 @@ impl Resolve<ExecuteArgs> for PullDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -482,10 +469,6 @@ impl Resolve<ExecuteArgs> for PullDeployment {
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -533,7 +516,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.starting = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -544,7 +527,7 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::StartContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -558,10 +541,6 @@ impl Resolve<ExecuteArgs> for StartDeployment {
|
||||
update.logs.push(log);
|
||||
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)
|
||||
@@ -609,7 +588,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.restarting = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -620,7 +599,7 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RestartContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -636,10 +615,6 @@ impl Resolve<ExecuteArgs> for RestartDeployment {
|
||||
update.logs.push(log);
|
||||
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)
|
||||
@@ -687,7 +662,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pausing = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -698,7 +673,7 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::PauseContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -712,10 +687,6 @@ impl Resolve<ExecuteArgs> for PauseDeployment {
|
||||
update.logs.push(log);
|
||||
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)
|
||||
@@ -763,7 +734,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.unpausing = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -774,7 +745,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::UnpauseContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -790,10 +761,6 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
update.logs.push(log);
|
||||
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)
|
||||
@@ -843,7 +810,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.stopping = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -854,7 +821,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
let log = match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::StopContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
signal: self
|
||||
.signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
@@ -876,10 +843,6 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
update.logs.push(log);
|
||||
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)
|
||||
@@ -905,7 +868,6 @@ impl Resolve<ExecuteArgs> for BatchDestroyDeployment {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -915,7 +877,6 @@ impl Resolve<ExecuteArgs> for BatchDestroyDeployment {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDestroyDeployment>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
@@ -961,7 +922,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.destroying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -975,7 +936,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
match swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
api::swarm::RemoveSwarmServices {
|
||||
services: vec![deployment.deployed_name().to_string()],
|
||||
services: vec![deployment.name],
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -996,7 +957,7 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
match periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RemoveContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
signal: self
|
||||
.signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
@@ -1022,33 +983,9 @@ impl Resolve<ExecuteArgs> for DestroyDeployment {
|
||||
}
|
||||
};
|
||||
|
||||
// Clear the tracked deployed name once the container / service
|
||||
// is confirmed removed, so the next deploy uses the fresh name.
|
||||
if log.success
|
||||
&& let Err(e) = resource::update_info::<Deployment>(
|
||||
&deployment.id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: deployment
|
||||
.info
|
||||
.latest_image_digest
|
||||
.clone(),
|
||||
deployed_name: Default::default(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Failed to clear deployment {} ({}) deployed name after destroy | {e:#}",
|
||||
deployment.name, deployment.id,
|
||||
);
|
||||
}
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{fmt::Write as _, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{CommandOptions, run_komodo_standard_command};
|
||||
use command::run_komodo_standard_command;
|
||||
use database::{
|
||||
bson::{Document, doc},
|
||||
mungos::find::find_collect,
|
||||
@@ -167,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;
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ use komodo_client::{
|
||||
entities::{
|
||||
Operation,
|
||||
permission::PermissionLevel,
|
||||
resource::ResourceQuery,
|
||||
update::{Log, Update},
|
||||
user::User,
|
||||
},
|
||||
@@ -30,10 +29,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::KomodoAuthImpl,
|
||||
helpers::{
|
||||
query::get_all_tags,
|
||||
update::{init_execution_update, update_update},
|
||||
},
|
||||
helpers::update::{init_execution_update, update_update},
|
||||
resource::{KomodoResource, list_full_for_user_using_pattern},
|
||||
state::db_client,
|
||||
};
|
||||
@@ -115,12 +111,10 @@ pub enum ExecuteRequest {
|
||||
// ==== PROCEDURE ====
|
||||
RunProcedure(RunProcedure),
|
||||
BatchRunProcedure(BatchRunProcedure),
|
||||
CancelProcedure(CancelProcedure),
|
||||
|
||||
// ==== ACTION ====
|
||||
RunAction(RunAction),
|
||||
BatchRunAction(BatchRunAction),
|
||||
CancelAction(CancelAction),
|
||||
|
||||
// ==== SYNC ====
|
||||
RunSync(RunSync),
|
||||
@@ -362,26 +356,14 @@ trait BatchExecute {
|
||||
#[instrument("BatchExecute", skip(user))]
|
||||
async fn batch_execute<E: BatchExecute>(
|
||||
pattern: &str,
|
||||
tags: Vec<String>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<BatchExecutionResponse> {
|
||||
let all_tags = if tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let resources = list_full_for_user_using_pattern::<E::Resource>(
|
||||
pattern,
|
||||
ResourceQuery {
|
||||
tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&all_tags,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::bson::to_document,
|
||||
};
|
||||
use formatting::{Color, bold, colored, format_serror, muted};
|
||||
use komodo_client::{
|
||||
api::execute::{
|
||||
BatchExecutionResponse, BatchRunProcedure, CancelProcedure,
|
||||
RunProcedure,
|
||||
BatchExecutionResponse, BatchRunProcedure, RunProcedure,
|
||||
},
|
||||
entities::{
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
@@ -21,14 +19,13 @@ use komodo_client::{
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
alert::send_alerts,
|
||||
helpers::{procedure::execute_procedure, update::update_update},
|
||||
permission::get_check_permissions,
|
||||
resource::refresh_procedure_state_cache,
|
||||
state::{action_states, db_client, procedure_cancel_cache},
|
||||
state::{action_states, db_client},
|
||||
};
|
||||
|
||||
use super::{ExecuteArgs, ExecuteRequest};
|
||||
@@ -51,12 +48,8 @@ impl Resolve<ExecuteArgs> for BatchRunProcedure {
|
||||
ExecuteArgs { user, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchRunProcedure>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchRunProcedure>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -124,22 +117,14 @@ fn resolve_inner(
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure procedure not already busy before updating.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.running = true)?;
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
|
||||
procedure_cancel_cache()
|
||||
.insert(procedure.id.clone(), cancel.clone())
|
||||
.await;
|
||||
|
||||
let update = Mutex::new(update);
|
||||
|
||||
let res = execute_procedure(&procedure, &update, cancel).await;
|
||||
|
||||
procedure_cancel_cache().remove(&procedure.id).await;
|
||||
let res = execute_procedure(&procedure, &update).await;
|
||||
|
||||
let mut update = update.into_inner();
|
||||
|
||||
@@ -155,15 +140,11 @@ fn resolve_inner(
|
||||
);
|
||||
}
|
||||
Err(e) => update
|
||||
.push_error_log("Execution error", format_serror(&e.into())),
|
||||
.push_error_log("execution error", format_serror(&e.into())),
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
@@ -203,47 +184,3 @@ fn resolve_inner(
|
||||
Ok(update)
|
||||
})
|
||||
}
|
||||
|
||||
impl Resolve<ExecuteArgs> for CancelProcedure {
|
||||
#[instrument(
|
||||
"CancelProcedure",
|
||||
skip_all,
|
||||
fields(
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
update_id = update.id,
|
||||
procedure = self.procedure,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id,
|
||||
}: &ExecuteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let procedure = get_check_permissions::<Procedure>(
|
||||
&self.procedure,
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
procedure_cancel_cache()
|
||||
.get(&procedure.id)
|
||||
.await
|
||||
.context("Procedure run cancel token not found")?
|
||||
.cancel();
|
||||
|
||||
let mut update = update.clone();
|
||||
update.push_simple_log(
|
||||
"Cancel Triggered",
|
||||
"Procedure cancel has been triggered. The procedure will exit after the currently running stage is complete.",
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ impl Resolve<ExecuteArgs> for BatchCloneRepo {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -66,12 +65,8 @@ impl Resolve<ExecuteArgs> for BatchCloneRepo {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchCloneRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchCloneRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +103,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.cloning = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -179,10 +174,6 @@ impl Resolve<ExecuteArgs> for CloneRepo {
|
||||
);
|
||||
};
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
handle_repo_update_return(update).await
|
||||
}
|
||||
}
|
||||
@@ -201,8 +192,7 @@ impl Resolve<ExecuteArgs> for BatchPullRepo {
|
||||
fields(
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
pattern = self.pattern
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -210,12 +200,8 @@ impl Resolve<ExecuteArgs> for BatchPullRepo {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchPullRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchPullRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -252,7 +238,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -327,10 +313,6 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
);
|
||||
};
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
handle_repo_update_return(update).await
|
||||
}
|
||||
}
|
||||
@@ -392,7 +374,6 @@ impl Resolve<ExecuteArgs> for BatchBuildRepo {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -400,12 +381,8 @@ impl Resolve<ExecuteArgs> for BatchBuildRepo {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchBuildRepo>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchBuildRepo>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -446,7 +423,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure repo not already busy before updating.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -513,7 +490,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
repo.name.clone(),
|
||||
None,
|
||||
builder,
|
||||
Some(&mut update),
|
||||
&mut update,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -524,9 +501,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
"get builder",
|
||||
format_serror(&e.context("failed to get builder").into()),
|
||||
));
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
return handle_builder_early_return(
|
||||
update, repo.id, repo.name, false,
|
||||
)
|
||||
@@ -559,9 +533,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
return handle_builder_early_return(update, repo.id, repo.name, true).await
|
||||
},
|
||||
};
|
||||
@@ -609,10 +580,6 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
|
||||
@@ -56,11 +56,8 @@ impl Resolve<ExecuteArgs> for StartContainer {
|
||||
|
||||
// Will check to ensure deployment not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.starting_containers += 1,
|
||||
|state| state.starting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.starting_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -86,10 +83,6 @@ impl Resolve<ExecuteArgs> for StartContainer {
|
||||
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)
|
||||
@@ -131,11 +124,8 @@ impl Resolve<ExecuteArgs> for RestartContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.restarting_containers += 1,
|
||||
|state| state.restarting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.restarting_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -163,10 +153,6 @@ impl Resolve<ExecuteArgs> for RestartContainer {
|
||||
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)
|
||||
@@ -208,11 +194,8 @@ impl Resolve<ExecuteArgs> for PauseContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.pausing_containers += 1,
|
||||
|state| state.pausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pausing_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -238,10 +221,6 @@ impl Resolve<ExecuteArgs> for PauseContainer {
|
||||
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)
|
||||
@@ -283,11 +262,8 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.unpausing_containers += 1,
|
||||
|state| state.unpausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.unpausing_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -315,10 +291,6 @@ impl Resolve<ExecuteArgs> for UnpauseContainer {
|
||||
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)
|
||||
@@ -362,11 +334,8 @@ impl Resolve<ExecuteArgs> for StopContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.stopping_containers += 1,
|
||||
|state| state.stopping_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.stopping_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -394,10 +363,6 @@ impl Resolve<ExecuteArgs> for StopContainer {
|
||||
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)
|
||||
@@ -447,11 +412,8 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.destroying_containers += 1,
|
||||
|state| state.destroying_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pruning_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -481,10 +443,6 @@ impl Resolve<ExecuteArgs> for DestroyContainer {
|
||||
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)
|
||||
@@ -525,11 +483,8 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.starting_containers += 1,
|
||||
|state| state.starting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.starting_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -552,10 +507,6 @@ impl Resolve<ExecuteArgs> for StartAllContainers {
|
||||
|
||||
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)
|
||||
@@ -596,11 +547,8 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.restarting_containers += 1,
|
||||
|state| state.restarting_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.restarting_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -625,10 +573,6 @@ impl Resolve<ExecuteArgs> for RestartAllContainers {
|
||||
|
||||
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)
|
||||
@@ -669,11 +613,8 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.pausing_containers += 1,
|
||||
|state| state.pausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pausing_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -696,10 +637,6 @@ impl Resolve<ExecuteArgs> for PauseAllContainers {
|
||||
|
||||
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)
|
||||
@@ -740,11 +677,8 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.unpausing_containers += 1,
|
||||
|state| state.unpausing_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.unpausing_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -769,10 +703,6 @@ impl Resolve<ExecuteArgs> for UnpauseAllContainers {
|
||||
|
||||
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)
|
||||
@@ -813,11 +743,8 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
|
||||
|
||||
// Will check to ensure server not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard = action_state.update_custom(
|
||||
|state| state.stopping_containers += 1,
|
||||
|state| state.stopping_containers -= 1,
|
||||
false,
|
||||
)?;
|
||||
let _action_guard = action_state
|
||||
.update(|state| state.stopping_containers = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
|
||||
@@ -840,10 +767,6 @@ impl Resolve<ExecuteArgs> for StopAllContainers {
|
||||
|
||||
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)
|
||||
@@ -884,7 +807,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();
|
||||
@@ -913,10 +836,6 @@ impl Resolve<ExecuteArgs> for PruneContainers {
|
||||
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)
|
||||
@@ -1022,7 +941,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();
|
||||
@@ -1049,10 +968,6 @@ impl Resolve<ExecuteArgs> for PruneNetworks {
|
||||
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)
|
||||
@@ -1155,7 +1070,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();
|
||||
@@ -1180,10 +1095,6 @@ impl Resolve<ExecuteArgs> for PruneImages {
|
||||
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)
|
||||
@@ -1289,7 +1200,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();
|
||||
@@ -1314,10 +1225,6 @@ impl Resolve<ExecuteArgs> for PruneVolumes {
|
||||
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)
|
||||
@@ -1358,7 +1265,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();
|
||||
@@ -1383,10 +1290,6 @@ impl Resolve<ExecuteArgs> for PruneDockerBuilders {
|
||||
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)
|
||||
@@ -1427,7 +1330,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();
|
||||
@@ -1452,10 +1355,6 @@ impl Resolve<ExecuteArgs> for PruneBuildx {
|
||||
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)
|
||||
@@ -1496,7 +1395,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();
|
||||
@@ -1520,10 +1419,6 @@ impl Resolve<ExecuteArgs> for PruneSystem {
|
||||
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)
|
||||
|
||||
@@ -72,7 +72,6 @@ impl Resolve<ExecuteArgs> for BatchDeployStack {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -80,12 +79,8 @@ impl Resolve<ExecuteArgs> for BatchDeployStack {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeployStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchDeployStack>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +131,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();
|
||||
@@ -330,10 +325,6 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -358,7 +349,6 @@ impl Resolve<ExecuteArgs> for BatchDeployStackIfChanged {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -368,7 +358,6 @@ impl Resolve<ExecuteArgs> for BatchDeployStackIfChanged {
|
||||
Ok(
|
||||
super::batch_execute::<BatchDeployStackIfChanged>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
@@ -788,7 +777,6 @@ impl Resolve<ExecuteArgs> for BatchPullStack {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -796,12 +784,8 @@ impl Resolve<ExecuteArgs> for BatchPullStack {
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchPullStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await?,
|
||||
super::batch_execute::<BatchPullStack>(&self.pattern, user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -959,7 +943,7 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
|
||||
// Will check to ensure stack not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.pulling = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -976,10 +960,6 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
|
||||
update.logs.extend(res.logs);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -1173,20 +1153,15 @@ impl Resolve<ExecuteArgs> for BatchDestroyStack {
|
||||
task_id = task_id.to_string(),
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
ExecuteArgs { user, task_id, .. }: &ExecuteArgs,
|
||||
) -> mogh_error::Result<BatchExecutionResponse> {
|
||||
super::batch_execute::<BatchDestroyStack>(
|
||||
&self.pattern,
|
||||
self.tags,
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
super::batch_execute::<BatchDestroyStack>(&self.pattern, user)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,7 +1214,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();
|
||||
@@ -1269,10 +1244,6 @@ impl Resolve<ExecuteArgs> for DestroyStack {
|
||||
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)
|
||||
@@ -1343,7 +1314,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
|
||||
let action_state =
|
||||
action_states().stack.get_or_insert_default(&stack.id).await;
|
||||
|
||||
let action_guard =
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let mut update = update.clone();
|
||||
@@ -1403,10 +1374,6 @@ impl Resolve<ExecuteArgs> for RunStackService {
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -98,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();
|
||||
@@ -311,10 +311,6 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
),
|
||||
);
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
return Ok(update);
|
||||
}
|
||||
@@ -446,10 +442,6 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Drop action guard before updating
|
||||
// clients to requery action state
|
||||
drop(action_guard);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::{Router, http::HeaderMap};
|
||||
use komodo_client::entities::resource::Resource;
|
||||
use mogh_cache::CloneCache;
|
||||
@@ -40,21 +41,12 @@ trait VerifySecret {
|
||||
/// Implemented on the integration struct, eg [integrations::github::Github]
|
||||
trait ExtractBranch {
|
||||
fn extract_branch(body: &str) -> anyhow::Result<String>;
|
||||
/// Whether the webhook body's branch matches `expected`.
|
||||
/// A mismatch is routine and only logged at debug; errors
|
||||
/// only when the branch cannot be extracted from the body.
|
||||
fn branch_matches(
|
||||
body: &str,
|
||||
expected: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
fn verify_branch(body: &str, expected: &str) -> anyhow::Result<()> {
|
||||
let branch = Self::extract_branch(body)?;
|
||||
if branch == expected {
|
||||
Ok(true)
|
||||
Ok(())
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{expected}'"
|
||||
);
|
||||
Ok(false)
|
||||
Err(anyhow!("request branch does not match expected"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{str::FromStr, sync::OnceLock, time::Duration};
|
||||
use std::{str::FromStr, sync::OnceLock};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use komodo_client::{
|
||||
@@ -23,7 +23,6 @@ use crate::{
|
||||
},
|
||||
helpers::update::init_execution_update,
|
||||
resource,
|
||||
state::action_states,
|
||||
};
|
||||
|
||||
use super::{ANY_BRANCH, ListenerLockCache};
|
||||
@@ -38,6 +37,11 @@ impl super::CustomSecret for Build {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_locks() -> &'static ListenerLockCache {
|
||||
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
build: Build,
|
||||
body: String,
|
||||
@@ -46,6 +50,12 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = build_locks().get_or_insert_default(&build.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
// Use the correct target branch when using linked repo.
|
||||
let branch = if build.config.linked_repo.is_empty() {
|
||||
build.config.branch
|
||||
@@ -57,49 +67,15 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
.branch
|
||||
};
|
||||
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Cancel if currently building
|
||||
if action_states()
|
||||
.build
|
||||
.get(&build.id)
|
||||
.await
|
||||
.and_then(|states| {
|
||||
states.get().ok().map(|states| states.building)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
let user = git_webhook_user().to_owned();
|
||||
let cancel = ExecuteRequest::CancelBuild(CancelBuild {
|
||||
build: build.id.clone(),
|
||||
});
|
||||
let update = init_execution_update(&cancel, &user).await?;
|
||||
let ExecuteRequest::CancelBuild(cancel) = cancel else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
cancel
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
task_id: Uuid::new_v4(),
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
|
||||
poll_build_until_cancelled(&build.id).await?;
|
||||
}
|
||||
B::verify_branch(&body, &branch)?;
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let run = ExecuteRequest::RunBuild(RunBuild { build: build.id });
|
||||
let update = init_execution_update(&run, &user).await?;
|
||||
let ExecuteRequest::RunBuild(run) = run else {
|
||||
let req = ExecuteRequest::RunBuild(RunBuild { build: build.id });
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunBuild(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
run
|
||||
req
|
||||
.resolve(&ExecuteArgs {
|
||||
user,
|
||||
update,
|
||||
@@ -107,32 +83,9 @@ pub async fn handle_build_webhook<B: super::ExtractBranch>(
|
||||
})
|
||||
.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
|
||||
// ======
|
||||
@@ -267,9 +220,7 @@ async fn handle_repo_webhook_inner<
|
||||
let lock = repo_locks().get_or_insert_default(&repo.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
if !B::branch_matches(&body, &repo.config.branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
B::verify_branch(&body, &repo.config.branch)?;
|
||||
|
||||
E::resolve(repo).await
|
||||
}
|
||||
@@ -400,9 +351,7 @@ pub async fn handle_stack_webhook_inner<
|
||||
.branch
|
||||
};
|
||||
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
B::verify_branch(&body, &branch)?;
|
||||
|
||||
E::resolve(stack).await.map_err(|e| e.error)
|
||||
}
|
||||
@@ -515,9 +464,7 @@ async fn handle_sync_webhook_inner<
|
||||
.branch
|
||||
};
|
||||
|
||||
if !B::branch_matches(&body, &branch)? {
|
||||
return Ok(());
|
||||
}
|
||||
B::verify_branch(&body, &branch)?;
|
||||
|
||||
E::resolve(sync).await
|
||||
}
|
||||
@@ -554,10 +501,8 @@ pub async fn handle_procedure_webhook<B: super::ExtractBranch>(
|
||||
procedure_locks().get_or_insert_default(&procedure.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
if target_branch != ANY_BRANCH
|
||||
&& !B::branch_matches(&body, target_branch)?
|
||||
{
|
||||
return Ok(());
|
||||
if target_branch != ANY_BRANCH {
|
||||
B::verify_branch(&body, target_branch)?;
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
@@ -612,10 +557,7 @@ pub async fn handle_action_webhook<B: super::ExtractBranch>(
|
||||
let branch = B::extract_branch(&body)?;
|
||||
|
||||
if target_branch != ANY_BRANCH && branch != target_branch {
|
||||
debug!(
|
||||
"Ignoring webhook | push to branch '{branch}' does not match expected branch '{target_branch}'"
|
||||
);
|
||||
return Ok(());
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
|
||||
@@ -3,8 +3,7 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
action::{
|
||||
Action, ActionActionState, ActionListItem, ActionSortBy,
|
||||
ActionState,
|
||||
Action, ActionActionState, ActionListItem, ActionState,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
@@ -12,13 +11,13 @@ use komodo_client::{
|
||||
use mogh_resolver::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::{get_action_state, get_all_tags},
|
||||
helpers::query::get_all_tags,
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
state::{action_state_cache, action_states},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetAction {
|
||||
async fn resolve(
|
||||
@@ -46,45 +45,15 @@ impl Resolve<ReadArgs> for ListActions {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ActionListItem> =
|
||||
match self.sort_by {
|
||||
ActionSortBy::Name => resource::ListItemSort::Name,
|
||||
ActionSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ActionSortBy::NextRun => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.next_scheduled_run
|
||||
.cmp(&b.info.next_scheduled_run)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let actions = resource::list_items_for_user::<Action>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|action| {
|
||||
states.is_empty() || states.contains(&action.info.state)
|
||||
},
|
||||
Ok(
|
||||
resource::list_for_user::<Action>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
Ok(actions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,28 +67,12 @@ impl Resolve<ReadArgs> for ListFullActions {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Action, _>(
|
||||
resource::list_full_for_user::<Action>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|action| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states.contains(&get_action_state(&action.id).await)
|
||||
{
|
||||
Some(action)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -154,8 +107,6 @@ impl Resolve<ReadArgs> for GetActionsSummary {
|
||||
) -> mogh_error::Result<GetActionsSummaryResponse> {
|
||||
let actions = resource::list_full_for_user::<Action>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Resolve<ReadArgs> for ListAlerts {
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "ts": -1 })
|
||||
.limit(NUM_ALERTS_PER_PAGE as i64)
|
||||
.skip(self.page.saturating_mul(NUM_ALERTS_PER_PAGE))
|
||||
.skip(self.page * NUM_ALERTS_PER_PAGE)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
alerter::{Alerter, AlerterListItem, AlerterSortBy},
|
||||
alerter::{Alerter, AlerterListItem},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
};
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetAlerter {
|
||||
async fn resolve(
|
||||
@@ -45,30 +45,12 @@ impl Resolve<ReadArgs> for ListAlerters {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<AlerterListItem> =
|
||||
match self.sort_by {
|
||||
AlerterSortBy::Name => resource::ListItemSort::Name,
|
||||
AlerterSortBy::Type => {
|
||||
resource::ListItemSort::DbField("config.endpoint.type")
|
||||
}
|
||||
AlerterSortBy::Enabled => {
|
||||
resource::ListItemSort::DbField("config.enabled")
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_items_for_user::<Alerter>(
|
||||
resource::list_for_user::<Alerter>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -85,12 +67,9 @@ impl Resolve<ReadArgs> for ListFullAlerters {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Alerter>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -106,8 +85,6 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetAlertersSummaryResponse> {
|
||||
let query = match list_resource_ids_for_user::<Alerter>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -11,9 +11,7 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
Operation,
|
||||
build::{
|
||||
Build, BuildActionState, BuildListItem, BuildSortBy, BuildState,
|
||||
},
|
||||
build::{Build, BuildActionState, BuildListItem, BuildState},
|
||||
permission::PermissionLevel,
|
||||
update::UpdateStatus,
|
||||
},
|
||||
@@ -27,7 +25,7 @@ use crate::{
|
||||
state::{action_states, build_state_cache, db_client},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetBuild {
|
||||
async fn resolve(
|
||||
@@ -55,47 +53,15 @@ impl Resolve<ReadArgs> for ListBuilds {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<BuildListItem> =
|
||||
match self.sort_by {
|
||||
BuildSortBy::Name => resource::ListItemSort::Name,
|
||||
BuildSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
BuildSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let builds = resource::list_items_for_user::<Build>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|build| states.is_empty() || states.contains(&build.info.state),
|
||||
Ok(
|
||||
resource::list_for_user::<Build>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
Ok(builds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,29 +75,12 @@ impl Resolve<ReadArgs> for ListFullBuilds {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Build, _>(
|
||||
resource::list_full_for_user::<Build>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|build| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&resource::get_build_state(&build.id).await)
|
||||
{
|
||||
Some(build)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -166,8 +115,6 @@ impl Resolve<ReadArgs> for GetBuildsSummary {
|
||||
) -> mogh_error::Result<GetBuildsSummaryResponse> {
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -217,8 +164,7 @@ impl Resolve<ReadArgs> for GetBuildMonthlyStats {
|
||||
let curr_ts = unix_timestamp_ms() as i64;
|
||||
let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS;
|
||||
|
||||
let close_ts =
|
||||
next_day - (self.page as i64).saturating_mul(30 * ONE_DAY_MS);
|
||||
let close_ts = next_day - self.page as i64 * 30 * ONE_DAY_MS;
|
||||
let open_ts = close_ts - 30 * ONE_DAY_MS;
|
||||
|
||||
let mut build_updates = db_client()
|
||||
@@ -335,8 +281,6 @@ impl Resolve<ReadArgs> for ListCommonBuildExtraArgs {
|
||||
};
|
||||
let builds = resource::list_full_for_user::<Build>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|
||||
@@ -4,7 +4,7 @@ use database::mungos::mongodb::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
builder::{Builder, BuilderListItem, BuilderSortBy},
|
||||
builder::{Builder, BuilderListItem},
|
||||
permission::PermissionLevel,
|
||||
},
|
||||
};
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetBuilder {
|
||||
async fn resolve(
|
||||
@@ -45,35 +45,12 @@ impl Resolve<ReadArgs> for ListBuilders {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<BuilderListItem> =
|
||||
match self.sort_by {
|
||||
BuilderSortBy::Name => resource::ListItemSort::Name,
|
||||
BuilderSortBy::Provider => {
|
||||
resource::ListItemSort::DbField("config.type")
|
||||
}
|
||||
BuilderSortBy::InstanceType => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.instance_type
|
||||
.cmp(&b.info.instance_type)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_items_for_user::<Builder>(
|
||||
resource::list_for_user::<Builder>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -90,12 +67,9 @@ impl Resolve<ReadArgs> for ListFullBuilders {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Builder>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -111,8 +85,6 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetBuildersSummaryResponse> {
|
||||
let query = match list_resource_ids_for_user::<Builder>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -6,8 +6,8 @@ use komodo_client::{
|
||||
entities::{
|
||||
SwarmOrServer,
|
||||
deployment::{
|
||||
Deployment, DeploymentActionState, DeploymentListItem,
|
||||
DeploymentSortBy, DeploymentState,
|
||||
Deployment, DeploymentActionState, DeploymentConfig,
|
||||
DeploymentListItem, DeploymentState,
|
||||
},
|
||||
docker::{
|
||||
container::{Container, ContainerStats},
|
||||
@@ -25,9 +25,7 @@ use reqwest::StatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_deployment_state},
|
||||
swarm::swarm_request,
|
||||
periphery_client, query::get_all_tags, swarm::swarm_request,
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource::{self, setup_deployment_execution},
|
||||
@@ -36,7 +34,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetDeployment {
|
||||
async fn resolve(
|
||||
@@ -65,66 +63,21 @@ impl Resolve<ReadArgs> for ListDeployments {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<DeploymentListItem> =
|
||||
match self.sort_by {
|
||||
DeploymentSortBy::Name => resource::ListItemSort::Name,
|
||||
DeploymentSortBy::Image => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.image
|
||||
.cmp(&b.info.image)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
DeploymentSortBy::Host => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
let host_a = if a.info.swarm_id.is_empty() {
|
||||
&a.info.server_name
|
||||
} else {
|
||||
&a.info.swarm_name
|
||||
};
|
||||
let host_b = if b.info.swarm_id.is_empty() {
|
||||
&b.info.server_name
|
||||
} else {
|
||||
&b.info.swarm_name
|
||||
};
|
||||
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
DeploymentSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| {
|
||||
// Use ! with update available to order 'true' first
|
||||
(!a.info.update_available)
|
||||
.cmp(&!b.info.update_available)
|
||||
})
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let deployments = resource::list_items_for_user::<Deployment>(
|
||||
let deployments = resource::list_for_user::<Deployment>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|deployment| {
|
||||
(!only_update_available || deployment.info.update_available)
|
||||
&& (states.is_empty()
|
||||
|| states.contains(&deployment.info.state))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let deployments = if only_update_available {
|
||||
deployments
|
||||
.into_iter()
|
||||
.filter(|deployment| deployment.info.update_available)
|
||||
.collect()
|
||||
} else {
|
||||
deployments
|
||||
};
|
||||
Ok(deployments)
|
||||
}
|
||||
}
|
||||
@@ -139,32 +92,12 @@ impl Resolve<ReadArgs> for ListFullDeployments {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Deployment, _>(
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|deployment| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states.contains(
|
||||
&get_deployment_state(&deployment.id)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
{
|
||||
Some(deployment)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -221,7 +154,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
|
||||
SwarmOrServer::Swarm(swarm) => swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLog {
|
||||
service: deployment.deployed_name().to_string(),
|
||||
service: deployment.name,
|
||||
tail,
|
||||
timestamps,
|
||||
no_task_ids: false,
|
||||
@@ -234,7 +167,7 @@ impl Resolve<ReadArgs> for GetDeploymentLog {
|
||||
SwarmOrServer::Server(server) => periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerLog {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
@@ -273,7 +206,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
|
||||
SwarmOrServer::Swarm(swarm) => swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::GetSwarmServiceLogSearch {
|
||||
service: deployment.deployed_name().to_string(),
|
||||
service: deployment.name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -288,7 +221,7 @@ impl Resolve<ReadArgs> for SearchDeploymentLog {
|
||||
SwarmOrServer::Server(server) => periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerLogSearch {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -341,7 +274,7 @@ impl Resolve<ReadArgs> for InspectDeploymentContainer {
|
||||
periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name,
|
||||
})
|
||||
.await
|
||||
.context("Failed to inspect container on server")
|
||||
@@ -374,7 +307,7 @@ impl Resolve<ReadArgs> for InspectDeploymentSwarmService {
|
||||
swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
periphery_client::api::swarm::InspectSwarmService {
|
||||
service: deployment.deployed_name().to_string(),
|
||||
service: deployment.name,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -388,24 +321,25 @@ impl Resolve<ReadArgs> for GetDeploymentStats {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ContainerStats> {
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
let Deployment {
|
||||
name,
|
||||
config: DeploymentConfig { server_id, .. },
|
||||
..
|
||||
} = get_check_permissions::<Deployment>(
|
||||
&self.deployment,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
if deployment.config.server_id.is_empty() {
|
||||
if server_id.is_empty() {
|
||||
return Err(
|
||||
anyhow!("Deployment has no Server attached").into(),
|
||||
anyhow!("deployment has no server attached").into(),
|
||||
);
|
||||
}
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
let server = resource::get::<Server>(&server_id).await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::GetContainerStats {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
})
|
||||
.request(api::container::GetContainerStats { name })
|
||||
.await
|
||||
.context("failed to get stats from periphery")?;
|
||||
Ok(res)
|
||||
@@ -440,8 +374,6 @@ impl Resolve<ReadArgs> for GetDeploymentsSummary {
|
||||
) -> mogh_error::Result<GetDeploymentsSummaryResponse> {
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -490,8 +422,6 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
|
||||
};
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|
||||
@@ -1,590 +0,0 @@
|
||||
use std::cmp;
|
||||
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use database::bson::doc;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
deployment::Deployment,
|
||||
docker::{
|
||||
container::{
|
||||
Container, ContainerListItem, ContainerSortBy,
|
||||
ContainerStateStatusEnum,
|
||||
},
|
||||
image::{Image, ImageHistoryResponseItem},
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
server::{Server, ServerQuery, ServerState},
|
||||
stack::{Stack, StackServiceNames},
|
||||
update::Log,
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api as periphery;
|
||||
|
||||
use crate::{
|
||||
api::read::{ReadArgs, list_limit},
|
||||
helpers::{periphery_client, query::get_all_tags},
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
resource,
|
||||
stack::compose_container_match_regex,
|
||||
state::{db_client, server_status_cache},
|
||||
};
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainersSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetContainersSummaryResponse> {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get servers from db")?;
|
||||
|
||||
let mut res = GetContainersSummaryResponse::default();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(docker) = &cache.docker {
|
||||
for container in &docker.containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
| ContainerStateStatusEnum::Paused
|
||||
| ContainerStateStatusEnum::Exited => res.stopped += 1,
|
||||
ContainerStateStatusEnum::Running => res.running += 1,
|
||||
ContainerStateStatusEnum::Empty => res.unknown += 1,
|
||||
_ => res.unhealthy += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListAllContainersResponse> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
ServerQuery::builder()
|
||||
.names(self.servers.clone())
|
||||
.tags(self.tags)
|
||||
.build(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut containers = Vec::<ContainerListItem>::new();
|
||||
let limit = list_limit(self.limit);
|
||||
// Match terms case insensitively.
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
let Some(docker) = &cache.docker else {
|
||||
continue;
|
||||
};
|
||||
containers.extend(
|
||||
docker
|
||||
.containers
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
// Apply state filter if defined.
|
||||
(self.state.is_empty() || self.state.contains(&container.state)) &&
|
||||
// Apply terms filter if defined
|
||||
(terms.is_empty()
|
||||
// Match when all terms contained within a name.
|
||||
|| {
|
||||
let name = container.name.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
})
|
||||
})
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
|
||||
// The containers all come from the in memory status cache,
|
||||
// so all matching containers are collected and sorted
|
||||
// before applying pagination.
|
||||
let compare = |a: &ContainerListItem, b: &ContainerListItem| {
|
||||
match self.sort_by {
|
||||
ContainerSortBy::Name => a.name.cmp(&b.name),
|
||||
ContainerSortBy::Server => a.server_name.cmp(&b.server_name),
|
||||
ContainerSortBy::State => a.state.cmp(&b.state),
|
||||
ContainerSortBy::Image => a.image.cmp(&b.image),
|
||||
ContainerSortBy::Networks => {
|
||||
a.networks.first().cmp(&b.networks.first())
|
||||
}
|
||||
ContainerSortBy::Ports => a
|
||||
.ports
|
||||
.first()
|
||||
.map(|port| port.private_port)
|
||||
.cmp(&b.ports.first().map(|port| port.private_port)),
|
||||
ContainerSortBy::Volumes => {
|
||||
a.volumes.first().cmp(&b.volumes.first())
|
||||
}
|
||||
}
|
||||
// Fall back to name based sorting for equal sort keys.
|
||||
// Inside `compare`, so descending sorts are fully descending,
|
||||
// matching the List<Resource> apis.
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
};
|
||||
if self.sort_desc {
|
||||
containers.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
containers.sort_by(|a, b| compare(a, b));
|
||||
}
|
||||
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(containers.into_iter().skip(skip).take(take).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListContainersResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.containers.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Container> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.inspect(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect container: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::InspectContainer {
|
||||
name: self.container,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// First check deployments with a matching deployed / custom container name.
|
||||
// The empty check is required to avoid matching
|
||||
// deployments with no custom name configured.
|
||||
if !self.container.is_empty()
|
||||
&& let Ok(Some(deployment)) = db_client()
|
||||
.deployments
|
||||
.find_one(doc! {
|
||||
"$or": [
|
||||
{ "info.deployed_name": &self.container },
|
||||
{ "config.custom_name": &self.container },
|
||||
]
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// Then check deployments matching by name
|
||||
if let Ok(deployment) =
|
||||
resource::get::<Deployment>(&self.container).await
|
||||
&& deployment.custom_name() == self.container
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// then check stacks
|
||||
let stacks = list_resources_for_user::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check matching stack
|
||||
for stack in stacks {
|
||||
for StackServiceNames {
|
||||
service_name,
|
||||
container_name,
|
||||
..
|
||||
} in stack
|
||||
.info
|
||||
.deployed_services
|
||||
.unwrap_or(stack.info.latest_services)
|
||||
{
|
||||
let is_match = match compose_container_match_regex(&container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
continue;
|
||||
}
|
||||
}.is_match(&self.container);
|
||||
|
||||
if is_match {
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Stack(stack.id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetResourceMatchingContainerResponse { resource: None })
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LOG_LENGTH: u64 = 5000;
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let GetContainerLog {
|
||||
server,
|
||||
container,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLog {
|
||||
name: container,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let SearchContainerLog {
|
||||
server,
|
||||
container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLogSearch {
|
||||
name: container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListComposeProjectsResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.projects.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListNetworks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListNetworksResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.networks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectNetwork {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Network> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect network: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectNetwork {
|
||||
name: self.network,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListImages {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListImagesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.images.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectImage {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Image> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect image: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectImage { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListImageHistory {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot get image history: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::ImageHistory { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListVolumes {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListVolumesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.volumes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectVolume {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Volume> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::docker::InspectVolume { name: self.volume })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use komodo_client::{
|
||||
ResourceTarget,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
config::{GitProvider, ImageRegistry},
|
||||
config::{DockerRegistry, GitProvider},
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
@@ -44,7 +44,6 @@ mod alerter;
|
||||
mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod docker;
|
||||
mod onboarding_key;
|
||||
mod permission;
|
||||
mod procedure;
|
||||
@@ -67,12 +66,6 @@ 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,
|
||||
@@ -87,8 +80,7 @@ enum ReadRequest {
|
||||
GetCoreInfo(GetCoreInfo),
|
||||
ListSecrets(ListSecrets),
|
||||
ListGitProvidersFromConfig(ListGitProvidersFromConfig),
|
||||
#[serde(alias = "ListDockerRegistriesFromConfig")]
|
||||
ListImageRegistriesFromConfig(ListImageRegistriesFromConfig),
|
||||
ListDockerRegistriesFromConfig(ListDockerRegistriesFromConfig),
|
||||
|
||||
// ==== SWARM ====
|
||||
GetSwarmsSummary(GetSwarmsSummary),
|
||||
@@ -125,33 +117,22 @@ enum ReadRequest {
|
||||
// ==== TERMINAL ====
|
||||
ListTerminals(ListTerminals),
|
||||
|
||||
// ==== CONTAINER ====
|
||||
#[serde(alias = "GetDockerContainersSummary")]
|
||||
GetContainersSummary(GetContainersSummary),
|
||||
#[serde(alias = "ListAllDockerContainers")]
|
||||
ListAllContainers(ListAllContainers),
|
||||
#[serde(alias = "ListDockerContainers")]
|
||||
ListContainers(ListContainers),
|
||||
#[serde(alias = "InspectDockerContainer")]
|
||||
InspectContainer(InspectContainer),
|
||||
// ==== DOCKER ====
|
||||
GetDockerContainersSummary(GetDockerContainersSummary),
|
||||
ListAllDockerContainers(ListAllDockerContainers),
|
||||
ListDockerContainers(ListDockerContainers),
|
||||
InspectDockerContainer(InspectDockerContainer),
|
||||
GetResourceMatchingContainer(GetResourceMatchingContainer),
|
||||
GetContainerLog(GetContainerLog),
|
||||
SearchContainerLog(SearchContainerLog),
|
||||
ListComposeProjects(ListComposeProjects),
|
||||
#[serde(alias = "ListDockerNetworks")]
|
||||
ListNetworks(ListNetworks),
|
||||
#[serde(alias = "InspectDockerNetwork")]
|
||||
InspectNetwork(InspectNetwork),
|
||||
#[serde(alias = "ListDockerImages")]
|
||||
ListImages(ListImages),
|
||||
#[serde(alias = "InspectDockerImage")]
|
||||
InspectImage(InspectImage),
|
||||
#[serde(alias = "ListDockerImageHistory")]
|
||||
ListImageHistory(ListImageHistory),
|
||||
#[serde(alias = "ListDockerVolumes")]
|
||||
ListVolumes(ListVolumes),
|
||||
#[serde(alias = "InspectDockerVolume")]
|
||||
InspectVolume(InspectVolume),
|
||||
ListDockerNetworks(ListDockerNetworks),
|
||||
InspectDockerNetwork(InspectDockerNetwork),
|
||||
ListDockerImages(ListDockerImages),
|
||||
InspectDockerImage(InspectDockerImage),
|
||||
ListDockerImageHistory(ListDockerImageHistory),
|
||||
ListDockerVolumes(ListDockerVolumes),
|
||||
InspectDockerVolume(InspectDockerVolume),
|
||||
|
||||
// ==== SERVER STATS ====
|
||||
GetSystemInformation(GetSystemInformation),
|
||||
@@ -170,7 +151,6 @@ enum ReadRequest {
|
||||
ListStacks(ListStacks),
|
||||
ListFullStacks(ListFullStacks),
|
||||
ListStackServices(ListStackServices),
|
||||
ListAllStackServices(ListAllStackServices),
|
||||
ListCommonStackExtraArgs(ListCommonStackExtraArgs),
|
||||
ListCommonStackBuildExtraArgs(ListCommonStackBuildExtraArgs),
|
||||
|
||||
@@ -278,10 +258,8 @@ enum ReadRequest {
|
||||
// ==== PROVIDER ====
|
||||
GetGitProviderAccount(GetGitProviderAccount),
|
||||
ListGitProviderAccounts(ListGitProviderAccounts),
|
||||
#[serde(alias = "GetDockerRegistryAccount")]
|
||||
GetImageRegistryAccount(GetImageRegistryAccount),
|
||||
#[serde(alias = "ListDockerRegistryAccounts")]
|
||||
ListImageRegistryAccounts(ListImageRegistryAccounts),
|
||||
GetDockerRegistryAccount(GetDockerRegistryAccount),
|
||||
ListDockerRegistryAccounts(ListDockerRegistryAccounts),
|
||||
|
||||
// ==== ONBOARDING KEY ====
|
||||
ListOnboardingKeys(ListOnboardingKeys),
|
||||
@@ -376,7 +354,6 @@ impl Resolve<ReadArgs> for GetCoreInfo {
|
||||
disable_websocket_reconnect: config.disable_websocket_reconnect,
|
||||
enable_fancy_toml: config.enable_fancy_toml,
|
||||
timezone: config.timezone.clone(),
|
||||
default_pagination_limit: config.default_pagination_limit,
|
||||
public_key: core_keys().load().public.to_string(),
|
||||
};
|
||||
Ok(info)
|
||||
@@ -402,9 +379,7 @@ impl Resolve<ReadArgs> for ListSecrets {
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => None,
|
||||
BuilderConfig::Server(config) => {
|
||||
config.server_ids.first().cloned()
|
||||
}
|
||||
BuilderConfig::Server(config) => Some(config.server_id),
|
||||
BuilderConfig::Aws(config) => {
|
||||
secrets.extend(config.secrets);
|
||||
None
|
||||
@@ -459,13 +434,11 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => {}
|
||||
BuilderConfig::Server(config) => {
|
||||
if let Some(server_id) = config.server_ids.first() {
|
||||
merge_git_providers_for_server(
|
||||
&mut providers,
|
||||
server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
merge_git_providers_for_server(
|
||||
&mut providers,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_git_providers(
|
||||
@@ -487,24 +460,18 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
let (builds, repos, syncs) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[]
|
||||
@@ -556,35 +523,33 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<ReadArgs> for ListImageRegistriesFromConfig {
|
||||
impl Resolve<ReadArgs> for ListDockerRegistriesFromConfig {
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &ReadArgs,
|
||||
) -> mogh_error::Result<ListImageRegistriesFromConfigResponse> {
|
||||
let mut registries = core_config().image_registries.clone();
|
||||
) -> mogh_error::Result<ListDockerRegistriesFromConfigResponse> {
|
||||
let mut registries = core_config().docker_registries.clone();
|
||||
|
||||
if let Some(target) = self.target {
|
||||
match target {
|
||||
ResourceTarget::Server(id) => {
|
||||
merge_image_registries_for_server(&mut registries, &id)
|
||||
merge_docker_registries_for_server(&mut registries, &id)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Url(_) => {}
|
||||
BuilderConfig::Server(config) => {
|
||||
if let Some(server_id) = config.server_ids.first() {
|
||||
merge_image_registries_for_server(
|
||||
&mut registries,
|
||||
server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
merge_docker_registries_for_server(
|
||||
&mut registries,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_image_registries(
|
||||
merge_docker_registries(
|
||||
&mut registries,
|
||||
config.image_registries,
|
||||
config.docker_registries,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -642,14 +607,14 @@ fn merge_git_providers(
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge_image_registries_for_server(
|
||||
registries: &mut Vec<ImageRegistry>,
|
||||
async fn merge_docker_registries_for_server(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
server_id: &str,
|
||||
) -> mogh_error::Result<()> {
|
||||
let server = resource::get::<Server>(server_id).await?;
|
||||
let more = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery_client::api::ListImageRegistries {})
|
||||
.request(periphery_client::api::ListDockerRegistries {})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -657,13 +622,13 @@ async fn merge_image_registries_for_server(
|
||||
server.name
|
||||
)
|
||||
})?;
|
||||
merge_image_registries(registries, more);
|
||||
merge_docker_registries(registries, more);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_image_registries(
|
||||
registries: &mut Vec<ImageRegistry>,
|
||||
more: Vec<ImageRegistry>,
|
||||
fn merge_docker_registries(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
more: Vec<DockerRegistry>,
|
||||
) {
|
||||
for incoming_registry in more {
|
||||
if let Some(registry) = registries
|
||||
|
||||
@@ -3,21 +3,19 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
procedure::{
|
||||
Procedure, ProcedureListItem, ProcedureSortBy, ProcedureState,
|
||||
},
|
||||
procedure::{Procedure, ProcedureState},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::{get_all_tags, get_procedure_state},
|
||||
helpers::query::get_all_tags,
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
state::{action_states, procedure_state_cache},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetProcedure {
|
||||
async fn resolve(
|
||||
@@ -45,45 +43,15 @@ impl Resolve<ReadArgs> for ListProcedures {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ProcedureListItem> =
|
||||
match self.sort_by {
|
||||
ProcedureSortBy::Name => resource::ListItemSort::Name,
|
||||
ProcedureSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ProcedureSortBy::NextRun => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.next_scheduled_run
|
||||
.cmp(&b.info.next_scheduled_run)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let procedures = resource::list_items_for_user::<Procedure>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|procedure| {
|
||||
states.is_empty() || states.contains(&procedure.info.state)
|
||||
},
|
||||
Ok(
|
||||
resource::list_for_user::<Procedure>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
Ok(procedures)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,29 +65,12 @@ impl Resolve<ReadArgs> for ListFullProcedures {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Procedure, _>(
|
||||
resource::list_full_for_user::<Procedure>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|procedure| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_procedure_state(&procedure.id).await)
|
||||
{
|
||||
Some(procedure)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -133,8 +84,6 @@ impl Resolve<ReadArgs> for GetProceduresSummary {
|
||||
) -> mogh_error::Result<GetProceduresSummaryResponse> {
|
||||
let procedures = resource::list_full_for_user::<Procedure>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -61,11 +61,11 @@ impl Resolve<ReadArgs> for ListGitProviderAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetImageRegistryAccount {
|
||||
impl Resolve<ReadArgs> for GetDockerRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetImageRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<GetDockerRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can read docker registry accounts")
|
||||
@@ -83,11 +83,11 @@ impl Resolve<ReadArgs> for GetImageRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListImageRegistryAccounts {
|
||||
impl Resolve<ReadArgs> for ListDockerRegistryAccounts {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListImageRegistryAccountsResponse> {
|
||||
) -> mogh_error::Result<ListDockerRegistryAccountsResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can read docker registry accounts")
|
||||
|
||||
@@ -3,9 +3,7 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
repo::{
|
||||
Repo, RepoActionState, RepoListItem, RepoSortBy, RepoState,
|
||||
},
|
||||
repo::{Repo, RepoActionState, RepoListItem, RepoState},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -17,7 +15,7 @@ use crate::{
|
||||
state::{action_states, repo_state_cache},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetRepo {
|
||||
async fn resolve(
|
||||
@@ -45,41 +43,15 @@ impl Resolve<ReadArgs> for ListRepos {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<RepoListItem> =
|
||||
match self.sort_by {
|
||||
RepoSortBy::Name => resource::ListItemSort::Name,
|
||||
RepoSortBy::Repo => {
|
||||
resource::ListItemSort::DbField("config.repo")
|
||||
}
|
||||
RepoSortBy::Branch => {
|
||||
resource::ListItemSort::DbField("config.branch")
|
||||
}
|
||||
RepoSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let repos = resource::list_items_for_user::<Repo>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|repo| states.is_empty() || states.contains(&repo.info.state),
|
||||
Ok(
|
||||
resource::list_for_user::<Repo>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
Ok(repos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,29 +65,12 @@ impl Resolve<ReadArgs> for ListFullRepos {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Repo, _>(
|
||||
resource::list_full_for_user::<Repo>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|repo| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&resource::get_repo_state(&repo.id).await)
|
||||
{
|
||||
Some(repo)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -150,8 +105,6 @@ impl Resolve<ReadArgs> for GetReposSummary {
|
||||
) -> mogh_error::Result<GetReposSummaryResponse> {
|
||||
let repos = resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
action::{Action, ActionQuerySpecifics},
|
||||
action::Action,
|
||||
permission::PermissionLevel,
|
||||
procedure::{Procedure, ProcedureQuerySpecifics},
|
||||
procedure::Procedure,
|
||||
resource::{ResourceQuery, TemplatesQueryBehavior},
|
||||
schedule::{Schedule, ScheduleSortBy},
|
||||
schedule::Schedule,
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -20,7 +18,7 @@ use crate::{
|
||||
schedule::get_schedule_item_info,
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for ListSchedules {
|
||||
async fn resolve(
|
||||
@@ -31,36 +29,24 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
let (actions, procedures) = tokio::try_join!(
|
||||
list_full_for_user::<Action>(
|
||||
ResourceQuery {
|
||||
names: Default::default(),
|
||||
templates: TemplatesQueryBehavior::Include,
|
||||
tag_behavior: self.tag_behavior,
|
||||
tags: self.tags.clone(),
|
||||
terms: self.terms.clone(),
|
||||
specific: ActionQuerySpecifics {
|
||||
scheduled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
specific: Default::default(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
),
|
||||
list_full_for_user::<Procedure>(
|
||||
ResourceQuery {
|
||||
names: Default::default(),
|
||||
templates: TemplatesQueryBehavior::Include,
|
||||
tag_behavior: self.tag_behavior,
|
||||
tags: self.tags.clone(),
|
||||
terms: self.terms,
|
||||
specific: ProcedureQuerySpecifics {
|
||||
scheduled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
specific: Default::default(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
&args.user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -110,51 +96,12 @@ impl Resolve<ReadArgs> for ListSchedules {
|
||||
let (actions, procedures) =
|
||||
tokio::join!(join_all(actions), join_all(procedures));
|
||||
|
||||
// The terms / scheduled filters are already applied
|
||||
// at the db level by the queries above.
|
||||
let mut schedules =
|
||||
actions.into_iter().chain(procedures).collect::<Vec<_>>();
|
||||
|
||||
// The schedules are composed in memory across resource types,
|
||||
// so all matching schedules are collected and sorted
|
||||
// before applying pagination.
|
||||
// All comparators fall back to name based sorting for equal
|
||||
// sort keys, inside `compare`, so descending sorts are fully
|
||||
// descending, matching the List<Resource> apis.
|
||||
let compare: fn(&Schedule, &Schedule) -> Ordering =
|
||||
match self.sort_by {
|
||||
ScheduleSortBy::Name => |a, b| a.name.cmp(&b.name),
|
||||
ScheduleSortBy::Schedule => |a, b| {
|
||||
a.schedule
|
||||
.cmp(&b.schedule)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
// Order unscheduled (None) last, matching the UI.
|
||||
ScheduleSortBy::NextRun => |a, b| {
|
||||
(a.next_scheduled_run.is_none(), a.next_scheduled_run)
|
||||
.cmp(&(
|
||||
b.next_scheduled_run.is_none(),
|
||||
b.next_scheduled_run,
|
||||
))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
ScheduleSortBy::Enabled => |a, b| {
|
||||
a.enabled.cmp(&b.enabled).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
};
|
||||
if self.sort_desc {
|
||||
schedules.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
schedules.sort_by(compare);
|
||||
}
|
||||
|
||||
let limit = list_limit(self.limit);
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(schedules.into_iter().skip(skip).take(take).collect())
|
||||
Ok(
|
||||
actions
|
||||
.into_iter()
|
||||
.chain(procedures)
|
||||
.filter(|s| !s.schedule.is_empty())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+546
-113
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
cmp,
|
||||
collections::HashMap,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
@@ -15,31 +15,47 @@ use database::mungos::{
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
ResourceTarget,
|
||||
deployment::Deployment,
|
||||
docker::{
|
||||
container::{
|
||||
Container, ContainerListItem, ContainerStateStatusEnum,
|
||||
},
|
||||
image::{Image, ImageHistoryResponseItem},
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
server::{
|
||||
Server, ServerActionState, ServerListItem, ServerSortBy,
|
||||
Server, ServerActionState, ServerListItem, ServerQuery,
|
||||
ServerState,
|
||||
},
|
||||
stats::{MinimalSystemStats, SystemInformation, SystemProcess},
|
||||
stack::{Stack, StackServiceNames},
|
||||
stats::{SystemInformation, SystemProcess},
|
||||
update::Log,
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCode;
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api as periphery;
|
||||
use periphery_client::api::{
|
||||
self as periphery,
|
||||
container::InspectContainer,
|
||||
docker::{
|
||||
ImageHistory, InspectImage, InspectNetwork, InspectVolume,
|
||||
},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_cached_server_state},
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
helpers::{periphery_client, query::get_all_tags},
|
||||
permission::{get_check_permissions, list_resources_for_user},
|
||||
resource,
|
||||
stack::compose_container_match_regex,
|
||||
state::{action_states, db_client, server_status_cache},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetServersSummary {
|
||||
async fn resolve(
|
||||
@@ -48,8 +64,6 @@ impl Resolve<ReadArgs> for GetServersSummary {
|
||||
) -> mogh_error::Result<GetServersSummaryResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -111,89 +125,15 @@ impl Resolve<ReadArgs> for ListServers {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ServerListItem> = match self
|
||||
.sort_by
|
||||
{
|
||||
ServerSortBy::Name => resource::ListItemSort::Name,
|
||||
ServerSortBy::Region => {
|
||||
resource::ListItemSort::DbField("config.region")
|
||||
}
|
||||
ServerSortBy::Version => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.version
|
||||
.cmp(&b.info.version)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ServerSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ServerSortBy::Cpu => stats_sort(|stats| stats.cpu_perc as f64),
|
||||
ServerSortBy::Memory => stats_sort(|stats| {
|
||||
usage_percent(stats.mem_used_gb, stats.mem_total_gb)
|
||||
}),
|
||||
ServerSortBy::Disk => stats_sort(|stats| {
|
||||
usage_percent(stats.disk_used_gb, stats.disk_total_gb)
|
||||
}),
|
||||
ServerSortBy::LoadAverage => {
|
||||
stats_sort(|stats| stats.load_average.one)
|
||||
}
|
||||
ServerSortBy::Network => stats_sort(|stats| {
|
||||
stats.network_ingress_bytes + stats.network_egress_bytes
|
||||
}),
|
||||
};
|
||||
let servers = resource::list_items_for_user::<Server>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|server| {
|
||||
states.is_empty() || states.contains(&server.info.state)
|
||||
},
|
||||
Ok(
|
||||
resource::list_for_user::<Server>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an in memory sort on the list item stats,
|
||||
/// matching the stats displayed on the server stats table.
|
||||
/// Servers without stats (unreachable / disabled) order last.
|
||||
fn stats_sort(
|
||||
metric: fn(&MinimalSystemStats) -> f64,
|
||||
) -> resource::ListItemSort<ServerListItem> {
|
||||
resource::ListItemSort::InMemory(Box::new(move |a, b| {
|
||||
match (a.info.stats.as_ref(), b.info.stats.as_ref()) {
|
||||
(Some(a_stats), Some(b_stats)) => {
|
||||
metric(a_stats).total_cmp(&metric(b_stats))
|
||||
}
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => Ordering::Equal,
|
||||
}
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
|
||||
fn usage_percent(used: f64, total: f64) -> f64 {
|
||||
if total > 0.0 {
|
||||
100.0 * used / total
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,29 +147,12 @@ impl Resolve<ReadArgs> for ListFullServers {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Server, _>(
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|server| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_cached_server_state(&server.id).await)
|
||||
{
|
||||
Some(server)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -416,7 +339,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
let curr_ts = unix_timestamp_ms() as i64;
|
||||
let mut curr_ts = curr_ts
|
||||
- curr_ts % granularity
|
||||
- granularity * (page as i64).saturating_mul(STATS_PER_PAGE);
|
||||
- granularity * STATS_PER_PAGE * page as i64;
|
||||
for _ in 0..STATS_PER_PAGE {
|
||||
ts_vec.push(curr_ts);
|
||||
curr_ts -= granularity;
|
||||
@@ -430,6 +353,7 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
},
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "ts": -1 })
|
||||
.skip(page as u64 * STATS_PER_PAGE as u64)
|
||||
.limit(STATS_PER_PAGE)
|
||||
.build(),
|
||||
)
|
||||
@@ -444,3 +368,512 @@ impl Resolve<ReadArgs> for GetHistoricalServerStats {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerContainersResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.containers.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListAllDockerContainersResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
ServerQuery::builder().names(self.servers.clone()).build(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut containers = Vec::<ContainerListItem>::new();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
let Some(docker) = &cache.docker else {
|
||||
continue;
|
||||
};
|
||||
let more = docker
|
||||
.containers
|
||||
.iter()
|
||||
.filter(|container| {
|
||||
self.containers.is_empty()
|
||||
|| self.containers.contains(&container.name)
|
||||
})
|
||||
.cloned();
|
||||
containers.extend(more);
|
||||
}
|
||||
|
||||
Ok(containers)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetDockerContainersSummaryResponse> {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get servers from db")?;
|
||||
|
||||
let mut res = GetDockerContainersSummaryResponse::default();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(docker) = &cache.docker {
|
||||
for container in &docker.containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
| ContainerStateStatusEnum::Paused
|
||||
| ContainerStateStatusEnum::Exited => res.stopped += 1,
|
||||
ContainerStateStatusEnum::Running => res.running += 1,
|
||||
ContainerStateStatusEnum::Empty => res.unknown += 1,
|
||||
_ => res.unhealthy += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Container> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.inspect(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect container: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectContainer {
|
||||
name: self.container,
|
||||
})
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LOG_LENGTH: u64 = 5000;
|
||||
|
||||
impl Resolve<ReadArgs> for GetContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let GetContainerLog {
|
||||
server,
|
||||
container,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLog {
|
||||
name: container,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchContainerLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Log> {
|
||||
let SearchContainerLog {
|
||||
server,
|
||||
container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
} = self;
|
||||
let server = get_check_permissions::<Server>(
|
||||
&server,
|
||||
user,
|
||||
PermissionLevel::Read.logs(),
|
||||
)
|
||||
.await?;
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(periphery::container::GetContainerLogSearch {
|
||||
name: container,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<GetResourceMatchingContainerResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
// first check deployments
|
||||
if let Ok(deployment) =
|
||||
resource::get::<Deployment>(&self.container).await
|
||||
{
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Deployment(deployment.id).into(),
|
||||
});
|
||||
}
|
||||
|
||||
// then check stacks
|
||||
let stacks = list_resources_for_user::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check matching stack
|
||||
for stack in stacks {
|
||||
for StackServiceNames {
|
||||
service_name,
|
||||
container_name,
|
||||
..
|
||||
} in stack
|
||||
.info
|
||||
.deployed_services
|
||||
.unwrap_or(stack.info.latest_services)
|
||||
{
|
||||
let is_match = match compose_container_match_regex(&container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
continue;
|
||||
}
|
||||
}.is_match(&self.container);
|
||||
|
||||
if is_match {
|
||||
return Ok(GetResourceMatchingContainerResponse {
|
||||
resource: ResourceTarget::Stack(stack.id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetResourceMatchingContainerResponse { resource: None })
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerNetworks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerNetworksResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.networks.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerNetwork {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Network> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot inspect network: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectNetwork { name: self.network })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerImages {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerImagesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.images.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerImage {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Image> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect image: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectImage { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerImageHistory {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<ImageHistoryResponseItem>> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot get image history: server is {:?}",
|
||||
cache.state
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(ImageHistory { name: self.image })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListDockerVolumes {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListDockerVolumesResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.volumes.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerVolume {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Volume> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if cache.state != ServerState::Ok {
|
||||
return Err(
|
||||
anyhow!("Cannot inspect volume: server is {:?}", cache.state)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let res = periphery_client(&server)
|
||||
.await?
|
||||
.request(InspectVolume { name: self.volume })
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListComposeProjects {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListComposeProjectsResponse> {
|
||||
let server = get_check_permissions::<Server>(
|
||||
&self.server,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
if let Some(docker) = &cache.docker {
|
||||
Ok(docker.projects.clone())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl Resolve<ReadArgs> for ListAllTerminals {
|
||||
// async fn resolve(
|
||||
// self,
|
||||
// args: &ReadArgs,
|
||||
// ) -> Result<Self::Response, Self::Error> {
|
||||
// // match self.tar
|
||||
// let mut terminals = resource::list_full_for_user::<Server>(
|
||||
// self.query, &args.user, &all_tags,
|
||||
// )
|
||||
// .await?
|
||||
// .into_iter()
|
||||
// .map(|server| async move {
|
||||
// (
|
||||
// list_terminals_inner(&server, self.fresh).await,
|
||||
// (server.id, server.name),
|
||||
// )
|
||||
// })
|
||||
// .collect::<FuturesUnordered<_>>()
|
||||
// .collect::<Vec<_>>()
|
||||
// .await
|
||||
// .into_iter()
|
||||
// .flat_map(|(terminals, server)| {
|
||||
// let terminals = terminals.ok()?;
|
||||
// Some((terminals, server))
|
||||
// })
|
||||
// .flat_map(|(terminals, (server_id, server_name))| {
|
||||
// terminals.into_iter().map(move |info| {
|
||||
// TerminalInfoWithServer::from_terminal_info(
|
||||
// &server_id,
|
||||
// &server_name,
|
||||
// info,
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// terminals.sort_by(|a, b| {
|
||||
// a.server_name.cmp(&b.server_name).then(a.name.cmp(&b.name))
|
||||
// });
|
||||
|
||||
// Ok(terminals)
|
||||
// }
|
||||
// }
|
||||
|
||||
+61
-205
@@ -9,10 +9,7 @@ use komodo_client::{
|
||||
container::Container, service::SwarmService, stack::SwarmStack,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
stack::{
|
||||
Stack, StackActionState, StackListItem, StackQuery,
|
||||
StackService, StackSortBy, StackState,
|
||||
},
|
||||
stack::{Stack, StackActionState, StackListItem, StackState},
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCodeError as _;
|
||||
@@ -25,9 +22,7 @@ use reqwest::StatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_all_tags, get_cached_stack_state},
|
||||
swarm::swarm_request,
|
||||
periphery_client, query::get_all_tags, swarm::swarm_request,
|
||||
},
|
||||
permission::get_check_permissions,
|
||||
resource,
|
||||
@@ -35,7 +30,7 @@ use crate::{
|
||||
state::{action_states, stack_status_cache},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetStack {
|
||||
async fn resolve(
|
||||
@@ -53,128 +48,6 @@ impl Resolve<ReadArgs> for GetStack {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<StackListItem>> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<StackListItem> =
|
||||
match self.sort_by {
|
||||
StackSortBy::Name => resource::ListItemSort::Name,
|
||||
StackSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
StackSortBy::Host => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
let host_a = if a.info.swarm_id.is_empty() {
|
||||
&a.info.server_name
|
||||
} else {
|
||||
&a.info.swarm_name
|
||||
};
|
||||
let host_b = if b.info.swarm_id.is_empty() {
|
||||
&b.info.server_name
|
||||
} else {
|
||||
&b.info.swarm_name
|
||||
};
|
||||
host_a.cmp(host_b).then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
StackSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| {
|
||||
// Use ! with update available to order 'true' first
|
||||
(!a.info.update_available())
|
||||
.cmp(&!b.info.update_available())
|
||||
})
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
let stacks = resource::list_items_for_user::<Stack>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|stack| {
|
||||
(!only_update_available
|
||||
|| stack
|
||||
.info
|
||||
.services
|
||||
.iter()
|
||||
.any(|service| service.update_available))
|
||||
&& (states.is_empty() || states.contains(&stack.info.state))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(stacks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListFullStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListFullStacksResponse> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let states = self.query.specific.states.clone();
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user_filtered::<Stack, _>(
|
||||
self.query,
|
||||
limit,
|
||||
self.page,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|stack| {
|
||||
let states = states.clone();
|
||||
async move {
|
||||
if states.is_empty()
|
||||
|| states
|
||||
.contains(&get_cached_stack_state(&stack.id).await)
|
||||
{
|
||||
Some(stack)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStackServices {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -199,75 +72,6 @@ impl Resolve<ReadArgs> for ListStackServices {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListAllStackServices {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListStackServicesResponse> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_for_user::<Stack>(
|
||||
StackQuery::builder()
|
||||
.names(self.stacks.clone())
|
||||
.tags(self.tags)
|
||||
.build(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut services = Vec::<StackService>::new();
|
||||
let mut skipped = 0;
|
||||
let limit = list_limit(self.limit);
|
||||
let limit_usize = limit as usize;
|
||||
// Eg. page 1 skips until after 100 services, page 2 after 200.
|
||||
let skip = limit.saturating_mul(self.page);
|
||||
// Match terms case insensitively.
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for stack in stacks {
|
||||
let cache =
|
||||
stack_status_cache().get_or_insert_default(&stack.id).await;
|
||||
let more = cache.curr.services
|
||||
.iter()
|
||||
.filter(|service| {
|
||||
// Apply state filter if defined.
|
||||
(self.state.is_empty() || self.state.contains(&service.state)) &&
|
||||
// Apply terms filter if defined
|
||||
(terms.is_empty()
|
||||
// Match when all terms contained within a name.
|
||||
|| {
|
||||
let name = service.service.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
})
|
||||
});
|
||||
for service in more {
|
||||
if skipped < skip {
|
||||
skipped += 1;
|
||||
} else {
|
||||
// push and maybe early return
|
||||
services.push(service.clone());
|
||||
if limit > 0 && services.len() >= limit_usize {
|
||||
return Ok(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetStackLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -535,8 +339,6 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -571,8 +373,6 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -595,6 +395,64 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<Vec<StackListItem>> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let only_update_available = self.query.specific.update_available;
|
||||
let stacks = resource::list_for_user::<Stack>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?;
|
||||
let stacks = if only_update_available {
|
||||
stacks
|
||||
.into_iter()
|
||||
.filter(|stack| {
|
||||
stack
|
||||
.info
|
||||
.services
|
||||
.iter()
|
||||
.any(|service| service.update_available)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
stacks
|
||||
};
|
||||
Ok(stacks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for ListFullStacks {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListFullStacksResponse> {
|
||||
let all_tags = if self.query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Stack>(
|
||||
self.query,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetStackActionState {
|
||||
async fn resolve(
|
||||
self,
|
||||
@@ -623,8 +481,6 @@ impl Resolve<ReadArgs> for GetStacksSummary {
|
||||
) -> mogh_error::Result<GetStacksSummaryResponse> {
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -3,9 +3,7 @@ use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
swarm::{
|
||||
Swarm, SwarmActionState, SwarmListItem, SwarmSortBy, SwarmState,
|
||||
},
|
||||
swarm::{Swarm, SwarmActionState, SwarmListItem, SwarmState},
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
@@ -17,7 +15,7 @@ use crate::{
|
||||
state::{action_states, server_status_cache, swarm_status_cache},
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetSwarm {
|
||||
async fn resolve(
|
||||
@@ -45,32 +43,12 @@ impl Resolve<ReadArgs> for ListSwarms {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<SwarmListItem> =
|
||||
match self.sort_by {
|
||||
SwarmSortBy::Name => resource::ListItemSort::Name,
|
||||
SwarmSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_items_for_user::<Swarm>(
|
||||
resource::list_for_user::<Swarm>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -87,12 +65,9 @@ impl Resolve<ReadArgs> for ListFullSwarms {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<Swarm>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -130,8 +105,6 @@ impl Resolve<ReadArgs> for GetSwarmsSummary {
|
||||
) -> mogh_error::Result<GetSwarmsSummaryResponse> {
|
||||
let swarms = resource::list_full_for_user::<Swarm>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -5,7 +5,6 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
sync::{
|
||||
ResourceSync, ResourceSyncActionState, ResourceSyncListItem,
|
||||
ResourceSyncSortBy,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -16,7 +15,7 @@ use crate::{
|
||||
resource, state::action_states,
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
impl Resolve<ReadArgs> for GetResourceSync {
|
||||
async fn resolve(
|
||||
@@ -44,52 +43,12 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
let sort_by: resource::ListItemSort<ResourceSyncListItem> =
|
||||
match self.sort_by {
|
||||
ResourceSyncSortBy::Name => resource::ListItemSort::Name,
|
||||
ResourceSyncSortBy::Source => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.files_on_host
|
||||
.cmp(&b.info.files_on_host)
|
||||
.then_with(|| {
|
||||
a.info.linked_repo_name.cmp(&b.info.linked_repo_name)
|
||||
})
|
||||
.then_with(|| a.info.repo.cmp(&b.info.repo))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ResourceSyncSortBy::Branch => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.branch
|
||||
.cmp(&b.info.branch)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
ResourceSyncSortBy::State => {
|
||||
resource::ListItemSort::InMemory(Box::new(|a, b| {
|
||||
a.info
|
||||
.state
|
||||
.cmp(&b.info.state)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
resource::list_items_for_user::<ResourceSync>(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
self.query,
|
||||
resource::ListItemsQueryOptions {
|
||||
limit,
|
||||
page: self.page,
|
||||
sort_desc: self.sort_desc,
|
||||
sort_by,
|
||||
},
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
|_| true,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -106,12 +65,9 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let limit = list_limit(self.limit);
|
||||
Ok(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
self.query,
|
||||
limit as i64,
|
||||
self.page.saturating_mul(limit),
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -150,8 +106,6 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
|
||||
let resource_syncs =
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use futures_util::{
|
||||
FutureExt, StreamExt as _, stream::FuturesUnordered,
|
||||
@@ -11,7 +9,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
terminal::{Terminal, TerminalSortBy, TerminalTarget},
|
||||
terminal::{Terminal, TerminalTarget},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
@@ -24,7 +22,7 @@ use crate::{
|
||||
resource,
|
||||
};
|
||||
|
||||
use super::{ReadArgs, list_limit};
|
||||
use super::ReadArgs;
|
||||
|
||||
//
|
||||
|
||||
@@ -33,111 +31,57 @@ impl Resolve<ReadArgs> for ListTerminals {
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> mogh_error::Result<ListTerminalsResponse> {
|
||||
let mut terminals = match self.target {
|
||||
None => {
|
||||
list_all_terminals_for_user(user, self.use_names).await?
|
||||
let Some(target) = self.target else {
|
||||
return list_all_terminals_for_user(user, self.use_names).await;
|
||||
};
|
||||
match &target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await
|
||||
}
|
||||
Some(target) => match &target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
.context("Must provide 'target.params.server'")
|
||||
.status_code(StatusCode::BAD_REQUEST)?;
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Container { server, .. } => {
|
||||
let server = get_check_permissions::<Server>(
|
||||
server,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
let server = resource::get::<Server>(&server).await?;
|
||||
list_terminals_on_server(&server, Some(target)).await?
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// The terminals come from Periphery agents rather than the db,
|
||||
// so the terms filter / sort / pagination are applied in memory.
|
||||
if !self.terms.is_empty() {
|
||||
let terms = self
|
||||
.terms
|
||||
.iter()
|
||||
.map(|term| term.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
terminals.retain(|terminal| {
|
||||
let name = terminal.name.to_lowercase();
|
||||
terms.iter().all(|term| name.contains(term))
|
||||
});
|
||||
}
|
||||
|
||||
// All comparators fall back to name based sorting for equal
|
||||
// sort keys, inside `compare`, so descending sorts are fully
|
||||
// descending, matching the List<Resource> apis.
|
||||
let compare: fn(&Terminal, &Terminal) -> Ordering =
|
||||
match self.sort_by {
|
||||
TerminalSortBy::Name => |a, b| a.name.cmp(&b.name),
|
||||
TerminalSortBy::Target => |a, b| {
|
||||
a.target.cmp(&b.target).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Command => |a, b| {
|
||||
a.command.cmp(&b.command).then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Size => |a, b| {
|
||||
a.stored_size_kb
|
||||
.total_cmp(&b.stored_size_kb)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
TerminalSortBy::Created => |a, b| {
|
||||
a.created_at
|
||||
.cmp(&b.created_at)
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
},
|
||||
};
|
||||
if self.sort_desc {
|
||||
terminals.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
terminals.sort_by(compare);
|
||||
}
|
||||
|
||||
let limit = list_limit(self.limit);
|
||||
let skip = limit.saturating_mul(self.page) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
Ok(terminals.into_iter().skip(skip).take(take).collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +92,6 @@ async fn list_all_terminals_for_user(
|
||||
let (mut servers, stacks, deployments) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
@@ -161,16 +103,12 @@ async fn list_all_terminals_for_user(
|
||||
.collect::<Vec<_>>())),
|
||||
resource::list_full_for_user::<Stack>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&[]
|
||||
@@ -201,7 +139,7 @@ async fn list_all_terminals_for_user(
|
||||
}
|
||||
}
|
||||
|
||||
let terminals = servers
|
||||
let mut terminals = servers
|
||||
.into_iter()
|
||||
.map(|(server, server_permission)| async move {
|
||||
(
|
||||
@@ -230,7 +168,6 @@ async fn list_all_terminals_for_user(
|
||||
server_id.clone()
|
||||
}),
|
||||
};
|
||||
terminal.target_name = Some(server_name.clone());
|
||||
terminal
|
||||
}),
|
||||
TerminalTarget::Container { container, .. } => {
|
||||
@@ -243,7 +180,6 @@ async fn list_all_terminals_for_user(
|
||||
},
|
||||
container,
|
||||
};
|
||||
terminal.target_name = Some(server_name.clone());
|
||||
terminal
|
||||
})
|
||||
}
|
||||
@@ -257,7 +193,6 @@ async fn list_all_terminals_for_user(
|
||||
},
|
||||
service,
|
||||
};
|
||||
terminal.target_name = Some(s.name.clone());
|
||||
terminal
|
||||
})
|
||||
}
|
||||
@@ -271,7 +206,6 @@ async fn list_all_terminals_for_user(
|
||||
d.id.clone()
|
||||
},
|
||||
};
|
||||
terminal.target_name = Some(d.name.clone());
|
||||
terminal
|
||||
},
|
||||
)
|
||||
@@ -286,6 +220,10 @@ async fn list_all_terminals_for_user(
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
terminals.sort_by(|a, b| {
|
||||
a.target.cmp(&b.target).then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
Ok(terminals)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ use crate::{
|
||||
resource,
|
||||
state::db_client,
|
||||
sync::{
|
||||
replace_ids::ReplaceIds,
|
||||
toml::{ToToml, convert_resource},
|
||||
user_groups::{convert_user_groups, user_group_to_toml},
|
||||
variables::variable_to_toml,
|
||||
@@ -55,8 +54,6 @@ async fn get_all_targets(
|
||||
targets.extend(
|
||||
resource::list_full_for_user::<$Type>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
&all_tags,
|
||||
@@ -145,7 +142,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
PermissionLevel::Read.into(),
|
||||
)
|
||||
.await?;
|
||||
$Type::replace_ids(&mut resource.config);
|
||||
$Type::replace_ids(&mut resource);
|
||||
let (deploy, after) = existing
|
||||
.as_ref()
|
||||
.and_then(|e| {
|
||||
@@ -203,7 +200,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
|| !sync.config.repo.is_empty()
|
||||
|| !sync.config.linked_repo.is_empty())
|
||||
{
|
||||
ResourceSync::replace_ids(&mut sync.config);
|
||||
ResourceSync::replace_ids(&mut sync);
|
||||
res.resource_syncs.push(convert_resource::<ResourceSync>(
|
||||
sync,
|
||||
false,
|
||||
|
||||
@@ -47,9 +47,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
query,
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.skip(
|
||||
(self.page as u64).saturating_mul(UPDATES_PER_PAGE as u64),
|
||||
)
|
||||
.skip(self.page as u64 * UPDATES_PER_PAGE as u64)
|
||||
.limit(UPDATES_PER_PAGE)
|
||||
.build(),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mongo_indexed::doc;
|
||||
use database::mungos::mongodb::bson::to_document;
|
||||
use database::{
|
||||
mongo_indexed::doc, mungos::mongodb::bson::oid::ObjectId,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
@@ -13,6 +15,7 @@ use komodo_client::{
|
||||
builder::{Builder, BuilderConfig},
|
||||
permission::PermissionLevel,
|
||||
repo::Repo,
|
||||
server::ServerState,
|
||||
update::Update,
|
||||
},
|
||||
};
|
||||
@@ -22,11 +25,12 @@ use periphery_client::api::build::{
|
||||
};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::helpers::builder::connect_builder_periphery;
|
||||
use crate::{
|
||||
config::core_config,
|
||||
connection::PeripheryConnectionArgs,
|
||||
helpers::{
|
||||
git_token,
|
||||
git_token, periphery_client,
|
||||
query::get_server_with_state,
|
||||
update::{add_update, make_update},
|
||||
},
|
||||
periphery::PeripheryClient,
|
||||
@@ -472,15 +476,48 @@ async fn get_on_host_periphery(
|
||||
.await
|
||||
.context("Failed to get builder")?;
|
||||
|
||||
if let BuilderConfig::Aws(_) = builder.config {
|
||||
return Err(anyhow!(
|
||||
"Files on host doesn't work with AWS builder"
|
||||
));
|
||||
match builder.config {
|
||||
BuilderConfig::Aws(_) => {
|
||||
Err(anyhow!("Files on host doesn't work with AWS builder"))
|
||||
}
|
||||
BuilderConfig::Url(config) => {
|
||||
// TODO: Ensure connection is actually established.
|
||||
// Builder id no good because it may be active for multiple connections.
|
||||
let periphery = PeripheryClient::new(
|
||||
PeripheryConnectionArgs::from_url_builder(
|
||||
&ObjectId::new().to_hex(),
|
||||
&config,
|
||||
),
|
||||
config.insecure_tls,
|
||||
)
|
||||
.await?;
|
||||
// Poll for connection to be estalished
|
||||
let mut err = None;
|
||||
for _ in 0..10 {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
match periphery.health_check().await {
|
||||
Ok(_) => return Ok(periphery),
|
||||
Err(e) => err = Some(e),
|
||||
};
|
||||
}
|
||||
Err(err.context("Missing error")?)
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_id.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Builder is type server, but has no server attached"
|
||||
));
|
||||
}
|
||||
let (server, state) =
|
||||
get_server_with_state(&config.server_id).await?;
|
||||
if state != ServerState::Ok {
|
||||
return Err(anyhow!(
|
||||
"Builder server is disabled or not reachable"
|
||||
));
|
||||
};
|
||||
periphery_client(&server).await
|
||||
}
|
||||
}
|
||||
|
||||
connect_builder_periphery(build.name.clone(), None, builder, None)
|
||||
.await
|
||||
.map(|(periphery, _)| periphery)
|
||||
}
|
||||
|
||||
/// The successful case will be included as Some(remote_contents).
|
||||
|
||||
@@ -15,7 +15,6 @@ use komodo_client::{
|
||||
docker::container::RestartPolicyNameEnum,
|
||||
komodo_timestamp, optional_string,
|
||||
permission::PermissionLevel,
|
||||
resource::ResourceQuery,
|
||||
server::{Server, ServerState},
|
||||
to_container_compatible_name,
|
||||
update::Update,
|
||||
@@ -31,9 +30,7 @@ use crate::{
|
||||
api::execute::{self, ExecuteRequest, ExecutionResult},
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{
|
||||
get_all_tags, get_deployment_state, get_swarm_or_server,
|
||||
},
|
||||
query::{get_deployment_state, get_swarm_or_server},
|
||||
registry_token,
|
||||
update::{add_update, make_update, poll_update_until_complete},
|
||||
},
|
||||
@@ -303,24 +300,10 @@ impl Resolve<WriteArgs> for RenameDeployment {
|
||||
|
||||
let name = to_container_compatible_name(&self.name);
|
||||
|
||||
let state = get_deployment_state(&deployment.id).await?;
|
||||
let container_state =
|
||||
get_deployment_state(&deployment.id).await?;
|
||||
|
||||
// When no custom name is configured, the container / service
|
||||
// follows the Deployment name, and must be kept matched:
|
||||
// - Server mode: rename the container to the new name.
|
||||
// - Swarm mode: services cannot be renamed, so the current
|
||||
// service name is pinned in info.deployed_name, and stays
|
||||
// in use until the next deploy recreates the service.
|
||||
// A configured custom name is unaffected by Deployment rename.
|
||||
let follows_name =
|
||||
deployment.config.custom_name.trim().is_empty();
|
||||
let deployed = state != DeploymentState::NotDeployed;
|
||||
|
||||
let rename_container = follows_name
|
||||
&& deployed
|
||||
&& !deployment.config.server_id.is_empty();
|
||||
|
||||
if rename_container && state == DeploymentState::Unknown {
|
||||
if container_state == DeploymentState::Unknown {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Cannot rename Deployment when container status is unknown"
|
||||
@@ -329,51 +312,27 @@ impl Resolve<WriteArgs> for RenameDeployment {
|
||||
);
|
||||
}
|
||||
|
||||
// Includes the Unknown state, in case a service
|
||||
// is still running under the current name.
|
||||
let pin_deployed_name = follows_name
|
||||
&& deployed
|
||||
&& !deployment.config.swarm_id.is_empty()
|
||||
&& deployment.info.deployed_name.is_empty();
|
||||
|
||||
let mut update =
|
||||
make_update(&deployment, Operation::RenameDeployment, user);
|
||||
|
||||
let mut set =
|
||||
doc! { "name": &name, "updated_at": komodo_timestamp() };
|
||||
if rename_container {
|
||||
// Container is renamed to the new name below.
|
||||
set.insert("info.deployed_name", name.as_str());
|
||||
} else if pin_deployed_name {
|
||||
set.insert("info.deployed_name", deployment.deployed_name());
|
||||
}
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().deployments,
|
||||
&deployment.id,
|
||||
database::mungos::update::Update::Set(set),
|
||||
database::mungos::update::Update::Set(
|
||||
doc! { "name": &name, "updated_at": komodo_timestamp() },
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("Failed to update Deployment name on db")?;
|
||||
|
||||
if pin_deployed_name {
|
||||
update.push_simple_log(
|
||||
"Pin Service Name",
|
||||
format!(
|
||||
"Swarm services cannot be renamed, so the current service name '{}' stays in use until the next deploy recreates the service under the new name.",
|
||||
deployment.deployed_name()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if rename_container {
|
||||
if container_state != DeploymentState::NotDeployed {
|
||||
let server =
|
||||
resource::get::<Server>(&deployment.config.server_id).await?;
|
||||
let log = periphery_client(&server)
|
||||
.await?
|
||||
.request(api::container::RenameContainer {
|
||||
curr_name: deployment.deployed_name().to_string(),
|
||||
curr_name: deployment.name.clone(),
|
||||
new_name: name.clone(),
|
||||
})
|
||||
.await
|
||||
@@ -506,7 +465,6 @@ pub async fn check_deployment_for_update_inner(
|
||||
&deployment.id,
|
||||
&DeploymentInfo {
|
||||
latest_image_digest: latest_digest.clone(),
|
||||
deployed_name: deployment.info.deployed_name.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -663,7 +621,6 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
|
||||
fields(
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
skip_auto_update = self.skip_auto_update,
|
||||
wait_for_auto_update = self.wait_for_auto_update,
|
||||
)
|
||||
@@ -672,23 +629,12 @@ impl Resolve<WriteArgs> for BatchCheckDeploymentForUpdate {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let deployments = list_full_for_user_using_pattern::<Deployment>(
|
||||
&self.pattern,
|
||||
ResourceQuery {
|
||||
tags: self.tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&all_tags,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -211,12 +211,9 @@ pub enum WriteRequest {
|
||||
CreateGitProviderAccount(CreateGitProviderAccount),
|
||||
UpdateGitProviderAccount(UpdateGitProviderAccount),
|
||||
DeleteGitProviderAccount(DeleteGitProviderAccount),
|
||||
#[serde(alias = "CreateDockerRegistryAccount")]
|
||||
CreateImageRegistryAccount(CreateImageRegistryAccount),
|
||||
#[serde(alias = "UpdateDockerRegistryAccount")]
|
||||
UpdateImageRegistryAccount(UpdateImageRegistryAccount),
|
||||
#[serde(alias = "DeleteDockerRegistryAccount")]
|
||||
DeleteImageRegistryAccount(DeleteImageRegistryAccount),
|
||||
CreateDockerRegistryAccount(CreateDockerRegistryAccount),
|
||||
UpdateDockerRegistryAccount(UpdateDockerRegistryAccount),
|
||||
DeleteDockerRegistryAccount(DeleteDockerRegistryAccount),
|
||||
|
||||
// ==== ALERT ====
|
||||
CloseAlert(CloseAlert),
|
||||
|
||||
@@ -347,9 +347,9 @@ async fn extract_resource_target_with_validation(
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("Failed to query db for swarms")?
|
||||
.context("No matching swarm found")?
|
||||
.context("No matching server found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Swarm, id))
|
||||
Ok((ResourceTargetVariant::Server, id))
|
||||
}
|
||||
ResourceTarget::Server(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
Operation, ResourceTarget,
|
||||
provider::{GitProviderAccount, ImageRegistryAccount},
|
||||
provider::{DockerRegistryAccount, GitProviderAccount},
|
||||
},
|
||||
};
|
||||
use mogh_error::AddStatusCodeError;
|
||||
@@ -248,7 +248,7 @@ impl Resolve<WriteArgs> for DeleteGitProviderAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CreateImageRegistryAccount {
|
||||
impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
#[instrument(
|
||||
"CreateDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -261,7 +261,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<CreateImageRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<CreateDockerRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!(
|
||||
@@ -271,7 +271,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
|
||||
);
|
||||
}
|
||||
|
||||
let mut account: ImageRegistryAccount = self.account.into();
|
||||
let mut account: DockerRegistryAccount = self.account.into();
|
||||
|
||||
if account.domain.is_empty() {
|
||||
return Err(
|
||||
@@ -326,7 +326,7 @@ impl Resolve<WriteArgs> for CreateImageRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
|
||||
impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
#[instrument(
|
||||
"UpdateDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -340,7 +340,7 @@ impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
|
||||
async fn resolve(
|
||||
mut self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<UpdateImageRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<UpdateDockerRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can update docker registry accounts")
|
||||
@@ -423,7 +423,7 @@ impl Resolve<WriteArgs> for UpdateImageRegistryAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for DeleteImageRegistryAccount {
|
||||
impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
|
||||
#[instrument(
|
||||
"DeleteDockerRegistryAccount",
|
||||
skip_all,
|
||||
@@ -435,7 +435,7 @@ impl Resolve<WriteArgs> for DeleteImageRegistryAccount {
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<DeleteImageRegistryAccountResponse> {
|
||||
) -> mogh_error::Result<DeleteDockerRegistryAccountResponse> {
|
||||
if !user.admin {
|
||||
return Err(
|
||||
anyhow!("Only admins can delete docker registry accounts")
|
||||
|
||||
@@ -19,7 +19,6 @@ 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},
|
||||
@@ -37,7 +36,7 @@ use crate::{
|
||||
api::execute::{self, ExecuteRequest, ExecutionResult},
|
||||
config::core_config,
|
||||
helpers::{
|
||||
query::{get_all_tags, get_swarm_or_server},
|
||||
query::get_swarm_or_server,
|
||||
stack_git_token, swarm_or_server_request,
|
||||
update::{add_update, make_update, poll_update_until_complete},
|
||||
},
|
||||
@@ -1041,7 +1040,6 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
|
||||
fields(
|
||||
operator = user.id,
|
||||
pattern = self.pattern,
|
||||
tags = self.tags.join(","),
|
||||
skip_auto_update = self.skip_auto_update,
|
||||
wait_for_auto_update = self.wait_for_auto_update,
|
||||
)
|
||||
@@ -1050,23 +1048,12 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
let all_tags = if self.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
|
||||
let stacks = list_full_for_user_using_pattern::<Stack>(
|
||||
&self.pattern,
|
||||
ResourceQuery {
|
||||
tags: self.tags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
user,
|
||||
PermissionLevel::Execute.into(),
|
||||
&all_tags,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -171,10 +171,10 @@ impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
mut self,
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> mogh_error::Result<NoData> {
|
||||
let server = match &mut self.target {
|
||||
let server = match &self.target {
|
||||
TerminalTarget::Server { server } => {
|
||||
let server = server
|
||||
.as_ref()
|
||||
@@ -195,31 +195,27 @@ impl Resolve<WriteArgs> for DeleteTerminal {
|
||||
)
|
||||
.await?
|
||||
}
|
||||
TerminalTarget::Stack {
|
||||
stack: stack_id, ..
|
||||
} => {
|
||||
let stack = get_check_permissions::<Stack>(
|
||||
stack_id,
|
||||
TerminalTarget::Stack { stack, .. } => {
|
||||
let server = get_check_permissions::<Stack>(
|
||||
stack,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
// Must fix any incoming stack name to id
|
||||
*stack_id = stack.id;
|
||||
resource::get::<Server>(&stack.config.server_id).await?
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
}
|
||||
TerminalTarget::Deployment {
|
||||
deployment: deployment_id,
|
||||
} => {
|
||||
let deployment = get_check_permissions::<Deployment>(
|
||||
deployment_id,
|
||||
TerminalTarget::Deployment { deployment } => {
|
||||
let server = get_check_permissions::<Deployment>(
|
||||
deployment,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
)
|
||||
.await?;
|
||||
// Must fix any incoming deployment name to id
|
||||
*deployment_id = deployment.id;
|
||||
resource::get::<Server>(&deployment.config.server_id).await?
|
||||
.await?
|
||||
.config
|
||||
.server_id;
|
||||
resource::get::<Server>(&server).await?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -293,8 +289,6 @@ impl Resolve<WriteArgs> for BatchDeleteAllTerminals {
|
||||
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.terminal(),
|
||||
&all_tags,
|
||||
|
||||
@@ -24,9 +24,6 @@ use crate::{
|
||||
mod terminal;
|
||||
mod update;
|
||||
|
||||
pub(super) const WS_KEEP_ALIVE_INTERVAL: std::time::Duration =
|
||||
std::time::Duration::from_secs(30);
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
// Periphery facing
|
||||
|
||||
@@ -117,25 +117,9 @@ async fn forward_ws_channel(
|
||||
};
|
||||
|
||||
let periphery_to_core = async {
|
||||
let mut keep_alive =
|
||||
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
|
||||
loop {
|
||||
// Already adheres to cancellation token
|
||||
let recv = tokio::select! {
|
||||
// Websocket keep-alive ping
|
||||
_ = keep_alive.tick() => {
|
||||
if client_send
|
||||
.send(ws::Message::Ping(Bytes::new()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
recv = periphery_receiver.recv() => recv,
|
||||
};
|
||||
match recv {
|
||||
match periphery_receiver.recv().await {
|
||||
Ok(Ok(bytes)) => {
|
||||
if let Err(e) =
|
||||
client_send.send(ws::Message::Binary(bytes.into())).await
|
||||
|
||||
@@ -3,7 +3,6 @@ use axum::{
|
||||
extract::{WebSocketUpgrade, ws::Message},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use komodo_client::entities::{
|
||||
ResourceTarget, permission::PermissionLevel, user::User,
|
||||
@@ -39,19 +38,10 @@ pub async fn handler(
|
||||
let cancel_clone = cancel.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut keep_alive =
|
||||
tokio::time::interval(super::WS_KEEP_ALIVE_INTERVAL);
|
||||
loop {
|
||||
// poll for updates off the receiver / await cancel / send keep-alive ping.
|
||||
// poll for updates off the receiver / await cancel.
|
||||
let update = select! {
|
||||
_ = cancel_clone.cancelled() => break,
|
||||
_ = keep_alive.tick() => {
|
||||
if ws_sender.send(Message::Ping(Bytes::new())).await.is_err() {
|
||||
cancel_clone.cancel();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
update = receiver.recv() => {update.expect("failed to recv update msg")}
|
||||
};
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn launch_ec2_instance(
|
||||
port: _,
|
||||
use_https: _,
|
||||
git_providers: _,
|
||||
image_registries: _,
|
||||
docker_registries: _,
|
||||
secrets: _,
|
||||
} = config;
|
||||
let instance_type = handle_unknown_instance_type(
|
||||
|
||||
@@ -2,9 +2,8 @@ pub mod aws;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BuildCleanupData {
|
||||
/// Store the builder id if it needs
|
||||
/// to be removed from builder_usage_cache.
|
||||
Server(Option<String>),
|
||||
/// Nothing to clean up
|
||||
Server,
|
||||
/// Cleanup Periphery connection
|
||||
Url,
|
||||
/// Clean up AWS instance
|
||||
|
||||
+1
-10
@@ -348,9 +348,6 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
transparent_mode: env
|
||||
.komodo_transparent_mode
|
||||
.unwrap_or(config.transparent_mode),
|
||||
default_pagination_limit: env
|
||||
.komodo_default_pagination_limit
|
||||
.unwrap_or(config.default_pagination_limit),
|
||||
ui_write_disabled: env
|
||||
.komodo_ui_write_disabled
|
||||
.unwrap_or(config.ui_write_disabled),
|
||||
@@ -433,12 +430,6 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
ssl_cert_file: env
|
||||
.komodo_ssl_cert_file
|
||||
.unwrap_or(config.ssl_cert_file),
|
||||
reporting_enabled: env
|
||||
.komodo_reporting_enabled
|
||||
.unwrap_or(config.reporting_enabled),
|
||||
reporting_private_key: env
|
||||
.komodo_reporting_private_key
|
||||
.unwrap_or(config.reporting_private_key),
|
||||
ui_path: env.komodo_ui_path.unwrap_or(config.ui_path),
|
||||
ui_index_force_no_cache: env
|
||||
.komodo_ui_index_force_no_cache
|
||||
@@ -456,7 +447,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
// These can't be overridden on env
|
||||
secrets: config.secrets,
|
||||
git_providers: config.git_providers,
|
||||
image_registries: config.image_registries,
|
||||
docker_registries: config.docker_registries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ async fn create_server_maybe_builder(
|
||||
name: server_query,
|
||||
config: PartialBuilderConfig::Server(
|
||||
PartialServerBuilderConfig {
|
||||
server_ids: Some(vec![server.id.clone()]),
|
||||
server_id: Some(server.id.clone()),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
+27
-112
@@ -1,18 +1,16 @@
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mungos::mongodb::bson::oid::ObjectId;
|
||||
use formatting::muted;
|
||||
use futures_util::{StreamExt, stream::FuturesOrdered};
|
||||
use komodo_client::entities::{
|
||||
Version,
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
komodo_timestamp,
|
||||
server::{Server, ServerState},
|
||||
server::Server,
|
||||
update::{Log, Update},
|
||||
};
|
||||
use periphery_client::api::{self, GetVersionResponse};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
cloud::{
|
||||
@@ -26,7 +24,6 @@ use crate::{
|
||||
helpers::update::update_update,
|
||||
periphery::PeripheryClient,
|
||||
resource,
|
||||
state::{builder_usage_cache, server_status_cache},
|
||||
};
|
||||
|
||||
use super::periphery_client;
|
||||
@@ -40,26 +37,25 @@ const BUILDER_POLL_MAX_TRIES: usize = 60;
|
||||
fields(
|
||||
resource_name,
|
||||
builder_id = builder.id,
|
||||
update_id = update.as_ref().map(|u| u.id.as_str())
|
||||
update_id = update.id
|
||||
)
|
||||
)]
|
||||
pub async fn connect_builder_periphery(
|
||||
// build: &Build,
|
||||
resource_name: String,
|
||||
version: Option<Version>,
|
||||
builder: Builder,
|
||||
update: Option<&mut Update>,
|
||||
update: &mut Update,
|
||||
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
|
||||
match builder.config {
|
||||
BuilderConfig::Aws(config) => {
|
||||
get_aws_builder(&resource_name, version, config, update).await
|
||||
}
|
||||
BuilderConfig::Url(config) => {
|
||||
if config.address.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Builder has not yet configured an address"
|
||||
));
|
||||
}
|
||||
// Builder id no good because it may be active for multiple connections.
|
||||
// TODO: Dont use builder id, or will be problems
|
||||
// with simultaneous spawned builders.
|
||||
let periphery = PeripheryClient::new(
|
||||
PeripheryConnectionArgs::from_url_builder(
|
||||
&ObjectId::new().to_hex(),
|
||||
@@ -68,66 +64,22 @@ pub async fn connect_builder_periphery(
|
||||
config.insecure_tls,
|
||||
)
|
||||
.await?;
|
||||
// Poll for connection to be estalished
|
||||
let mut err = None;
|
||||
for _ in 0..10 {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
match periphery
|
||||
.health_check()
|
||||
.await
|
||||
.context("Url Builder failed health check")
|
||||
{
|
||||
Ok(_) => return Ok((periphery, BuildCleanupData::Url)),
|
||||
Err(e) => err = Some(e),
|
||||
};
|
||||
}
|
||||
Err(err.context("Missing error")?)
|
||||
periphery
|
||||
.health_check()
|
||||
.await
|
||||
.context("Url Builder failed health check")?;
|
||||
Ok((periphery, BuildCleanupData::Url))
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_ids.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Server Builder has no configured Servers"
|
||||
));
|
||||
if config.server_id.is_empty() {
|
||||
return Err(anyhow!("Builder has not configured a server"));
|
||||
}
|
||||
|
||||
// Short path for single configured builder
|
||||
if config.server_ids.len() == 1 {
|
||||
let server =
|
||||
resource::get::<Server>(&config.server_ids[0]).await?;
|
||||
let periphery = periphery_client(&server).await?;
|
||||
return Ok((periphery, BuildCleanupData::Server(None)));
|
||||
}
|
||||
|
||||
// Get filtered list of available Servers
|
||||
let server_status_cache = server_status_cache();
|
||||
let available_server_ids = config
|
||||
.server_ids
|
||||
.iter()
|
||||
.map(|server_id| async move {
|
||||
server_status_cache
|
||||
.get(server_id)
|
||||
.await
|
||||
.map(|s| matches!(s.state, ServerState::Ok))
|
||||
.unwrap_or_default()
|
||||
.then_some(server_id)
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let selected = builder_usage_cache()
|
||||
.get_or_insert_default(&builder.id)
|
||||
.await
|
||||
.select(&available_server_ids)
|
||||
.await
|
||||
.context("Server builder has no available servers")?;
|
||||
|
||||
let server = resource::get::<Server>(selected).await?;
|
||||
let server = resource::get::<Server>(&config.server_id).await?;
|
||||
let periphery = periphery_client(&server).await?;
|
||||
Ok((periphery, BuildCleanupData::Server(Some(server.id))))
|
||||
Ok((periphery, BuildCleanupData::Server))
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
get_aws_builder(&resource_name, version, config, update).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,14 +89,14 @@ pub async fn connect_builder_periphery(
|
||||
skip_all,
|
||||
fields(
|
||||
resource_name,
|
||||
update_id = update.as_ref().map(|u| u.id.as_str()),
|
||||
update_id = update.id,
|
||||
)
|
||||
)]
|
||||
async fn get_aws_builder(
|
||||
resource_name: &str,
|
||||
version: Option<Version>,
|
||||
config: AwsBuilderConfig,
|
||||
mut update: Option<&mut Update>,
|
||||
update: &mut Update,
|
||||
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
|
||||
let start_create_ts = komodo_timestamp();
|
||||
|
||||
@@ -162,10 +114,9 @@ async fn get_aws_builder(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(update) = &mut update {
|
||||
update.logs.push(log);
|
||||
update_update((*update).clone()).await?;
|
||||
}
|
||||
update.logs.push(log);
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let protocol = if config.use_https { "wss" } else { "ws" };
|
||||
|
||||
@@ -202,10 +153,8 @@ async fn get_aws_builder(
|
||||
end_ts: komodo_timestamp(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(update) = update {
|
||||
update.logs.push(connect_log);
|
||||
update_update(update.clone()).await?;
|
||||
}
|
||||
update.logs.push(connect_log);
|
||||
update_update(update.clone()).await?;
|
||||
return Ok((
|
||||
periphery,
|
||||
BuildCleanupData::Aws {
|
||||
@@ -245,17 +194,9 @@ pub async fn cleanup_builder_instance(
|
||||
update: &mut Update,
|
||||
) {
|
||||
match cleanup_data {
|
||||
BuildCleanupData::Server(None) => {
|
||||
BuildCleanupData::Server => {
|
||||
// Nothing to clean up
|
||||
}
|
||||
BuildCleanupData::Server(Some(builder_id)) => {
|
||||
// Release periphery (server) id from builder
|
||||
builder_usage_cache()
|
||||
.get_or_insert_default(&builder_id)
|
||||
.await
|
||||
.release(&periphery.id)
|
||||
.await
|
||||
}
|
||||
BuildCleanupData::Url => {
|
||||
periphery.cleanup().await;
|
||||
}
|
||||
@@ -311,29 +252,3 @@ pub fn start_aws_builder_log(
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BuilderUsage(Mutex<HashMap<String, usize>>);
|
||||
|
||||
impl BuilderUsage {
|
||||
pub async fn select<'a>(
|
||||
&self,
|
||||
available: &'a [&String],
|
||||
) -> Option<&'a str> {
|
||||
let mut lock = self.0.lock().await;
|
||||
let selected = *available.iter().min_by_key(|key| {
|
||||
lock.get(key.as_str()).copied().unwrap_or(0)
|
||||
})?;
|
||||
*lock.entry(selected.clone()).or_insert(0) += 1;
|
||||
Some(selected.as_str())
|
||||
}
|
||||
|
||||
pub async fn release(&self, key: &str) {
|
||||
let mut lock = self.0.lock().await;
|
||||
if let Some(count) = lock.get_mut(key)
|
||||
&& *count > 0
|
||||
{
|
||||
*count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ pub async fn registry_token(
|
||||
}
|
||||
Ok(
|
||||
core_config()
|
||||
.image_registries
|
||||
.docker_registries
|
||||
.iter()
|
||||
.find(|provider| provider.domain == provider_domain)
|
||||
.and_then(|provider| {
|
||||
|
||||
@@ -14,13 +14,12 @@ use komodo_client::{
|
||||
procedure::{Procedure, ProcedureStage},
|
||||
repo::Repo,
|
||||
stack::Stack,
|
||||
update::Update,
|
||||
update::{Log, Update},
|
||||
user::procedure_user,
|
||||
},
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@@ -28,7 +27,6 @@ use crate::{
|
||||
execute::{ExecuteArgs, ExecuteRequest},
|
||||
write::WriteArgs,
|
||||
},
|
||||
config::core_config,
|
||||
resource::{KomodoResource, list_full_for_user_using_pattern},
|
||||
state::{all_resources_cache, db_client},
|
||||
};
|
||||
@@ -38,21 +36,8 @@ use super::update::{init_execution_update, update_update};
|
||||
pub async fn execute_procedure(
|
||||
procedure: &Procedure,
|
||||
update: &Mutex<Update>,
|
||||
cancel: CancellationToken,
|
||||
) -> anyhow::Result<()> {
|
||||
for stage in &procedure.config.stages {
|
||||
if cancel.is_cancelled() {
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"{}: Cancelled before stage: '{}'",
|
||||
muted("ERROR"),
|
||||
bold(&stage.name)
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return Err(anyhow!("Procedure cancelled before completion"));
|
||||
}
|
||||
if !stage.enabled {
|
||||
continue;
|
||||
}
|
||||
@@ -289,9 +274,6 @@ async fn execute_execution(
|
||||
}
|
||||
resolve_execute!(RunProcedure, req)
|
||||
}
|
||||
Execution::CancelProcedure(req) => {
|
||||
resolve_execute!(CancelProcedure, req)
|
||||
}
|
||||
// Special: write operation
|
||||
Execution::CommitSync(req) => req
|
||||
.resolve(&WriteArgs { user })
|
||||
@@ -344,9 +326,6 @@ async fn execute_execution(
|
||||
}
|
||||
// Standard executions
|
||||
Execution::RunAction(req) => resolve_execute!(RunAction, req),
|
||||
Execution::CancelAction(req) => {
|
||||
resolve_execute!(CancelAction, req)
|
||||
}
|
||||
Execution::RunBuild(req) => resolve_execute!(RunBuild, req),
|
||||
Execution::CancelBuild(req) => resolve_execute!(CancelBuild, req),
|
||||
Execution::Deploy(req) => resolve_execute!(Deploy, req),
|
||||
@@ -506,10 +485,9 @@ async fn execute_execution(
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"{}: Execution not successful, see update: '{}/updates/{}'",
|
||||
"{}: execution not successful. see update '{}'",
|
||||
colored("ERROR", Color::Red),
|
||||
core_config().host,
|
||||
update.id,
|
||||
bold(&update.id),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -523,13 +501,14 @@ async fn handle_resolve_result(
|
||||
match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
let log =
|
||||
Log::error("execution error", format_serror(&e.into()));
|
||||
let mut update =
|
||||
find_one_by_id(&db_client().updates, update_id)
|
||||
.await
|
||||
.context("Failed to query to db")?
|
||||
.context("no update exists with given id")?;
|
||||
update
|
||||
.push_error_log("Execution error", format_serror(&e.into()));
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
@@ -557,8 +536,6 @@ async fn extend_batch_exection<E: ExtendBatch>(
|
||||
let more = list_full_for_user_using_pattern::<E::Resource>(
|
||||
pattern,
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
procedure_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -744,9 +721,7 @@ pub fn replace_procedure_stage_ids_with_names(
|
||||
|
||||
replace_id_with_name!(
|
||||
RunProcedure => procedure, procedures;
|
||||
CancelProcedure => procedure, procedures;
|
||||
RunAction => action, actions;
|
||||
CancelAction => action, actions;
|
||||
RunBuild => build, builds;
|
||||
CancelBuild => build, builds;
|
||||
Deploy => deployment, deployments;
|
||||
|
||||
@@ -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,
|
||||
server_status_cache, stack_status_cache,
|
||||
stack_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -97,38 +97,6 @@ pub async fn get_server_state(server: &Server) -> ServerState {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Server state as computed for the server list items,
|
||||
/// from the in memory status cache.
|
||||
pub async fn get_cached_server_state(id: &String) -> ServerState {
|
||||
server_status_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.map(|status| status.state)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The Stack state as computed for the stack list items,
|
||||
/// from the in memory action states / status cache.
|
||||
pub async fn get_cached_stack_state(id: &String) -> StackState {
|
||||
if action_states()
|
||||
.stack
|
||||
.get(id)
|
||||
.await
|
||||
.map(|s| s.get().map(|s| s.deploying))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return StackState::Deploying;
|
||||
}
|
||||
stack_status_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.map(|status| status.curr.state)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn get_deployment_state(
|
||||
id: &String,
|
||||
) -> anyhow::Result<DeploymentState> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use komodo_client::entities::server::{Server, ServerState};
|
||||
use mogh_resolver::HasResponse;
|
||||
@@ -11,6 +13,23 @@ pub async fn swarm_request<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
swarm_request_custom_timeout(
|
||||
server_ids,
|
||||
request,
|
||||
Duration::from_secs(10),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn swarm_request_custom_timeout<T>(
|
||||
server_ids: &[String],
|
||||
request: T,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Clone + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
@@ -35,7 +54,10 @@ where
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match periphery.request(request.clone()).await {
|
||||
match periphery
|
||||
.request_custom_timeout(request.clone(), timeout)
|
||||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ pub async fn get_deployment_periphery_container(
|
||||
|
||||
let periphery = periphery_client(&server).await?;
|
||||
|
||||
let container = deployment.deployed_name().to_string();
|
||||
let container = deployment.name.clone();
|
||||
|
||||
Ok((
|
||||
TerminalTarget::Deployment {
|
||||
|
||||
@@ -226,10 +226,8 @@ pub async fn init_execution_update(
|
||||
(CancelRepoBuild, Repo, repo),
|
||||
// Procedure
|
||||
(RunProcedure, Procedure, procedure),
|
||||
(CancelProcedure, Procedure, procedure),
|
||||
// Action
|
||||
(RunAction, Action, action),
|
||||
(CancelAction, Action, action),
|
||||
// Resource Sync
|
||||
(RunSync, ResourceSync, sync),
|
||||
// Stack (simple)
|
||||
|
||||
@@ -19,7 +19,6 @@ mod monitor;
|
||||
mod network;
|
||||
mod periphery;
|
||||
mod permission;
|
||||
mod reporting;
|
||||
mod resource;
|
||||
mod schedule;
|
||||
mod stack;
|
||||
@@ -72,7 +71,6 @@ async fn app() -> anyhow::Result<()> {
|
||||
resource::spawn_action_state_refresh_loop();
|
||||
schedule::spawn_schedule_executor();
|
||||
helpers::prune::spawn_prune_loop();
|
||||
reporting::spawn_reporting_loop();
|
||||
}
|
||||
.instrument(startup_span)
|
||||
.await;
|
||||
|
||||
@@ -36,8 +36,6 @@ async fn get_all_swarms_map()
|
||||
-> anyhow::Result<(HashMap<String, Swarm>, HashMap<String, String>)> {
|
||||
let swarms = resource::list_full_for_user::<Swarm>(
|
||||
ResourceQuery::default(),
|
||||
None,
|
||||
None,
|
||||
system_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
@@ -63,8 +61,6 @@ async fn get_all_servers_map()
|
||||
{
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
ResourceQuery::default(),
|
||||
None,
|
||||
None,
|
||||
system_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
|
||||
@@ -52,18 +52,17 @@ pub fn spawn_monitoring_loops() {
|
||||
|
||||
fn spawn_server_monitoring_loop() {
|
||||
tokio::spawn(async move {
|
||||
refresh_all_server_cache().await;
|
||||
refresh_all_server_cache(komodo_timestamp()).await;
|
||||
let interval = monitoring_interval();
|
||||
loop {
|
||||
let ts = (wait_until_timelength(interval, ADDITIONAL_MS).await
|
||||
- ADDITIONAL_MS) as i64;
|
||||
refresh_all_server_cache().await;
|
||||
tokio::join!(check_alerts(ts), record_server_stats(ts));
|
||||
refresh_all_server_cache(ts).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn refresh_all_server_cache() {
|
||||
async fn refresh_all_server_cache(ts: i64) {
|
||||
let servers =
|
||||
match find_collect(&db_client().servers, None, None).await {
|
||||
Ok(servers) => servers,
|
||||
@@ -78,6 +77,7 @@ async fn refresh_all_server_cache() {
|
||||
refresh_server_cache(&server, false).await;
|
||||
});
|
||||
join_all(futures).await;
|
||||
tokio::join!(check_alerts(ts), record_server_stats(ts));
|
||||
}
|
||||
|
||||
/// Makes sure cache for server doesn't update too frequently / simultaneously.
|
||||
@@ -193,8 +193,7 @@ pub fn refresh_server_cache(
|
||||
|
||||
if let Some(docker) = &mut docker {
|
||||
docker.containers.iter_mut().for_each(|container| {
|
||||
container.server_id = Some(server.id.clone());
|
||||
container.server_name = Some(server.name.clone());
|
||||
container.server_id = Some(server.id.clone())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,9 @@ pub async fn record_server_stats(ts: i64) {
|
||||
ts,
|
||||
sid: status.id.clone(),
|
||||
cpu_perc: stats.cpu_perc,
|
||||
load_average: stats.load_average,
|
||||
load_average: stats.load_average.clone(),
|
||||
mem_total_gb: stats.mem_total_gb,
|
||||
mem_used_gb: stats.mem_used_gb,
|
||||
mem_buff_cache_gb: stats.mem_buff_cache_gb,
|
||||
mem_zfs_arc_gb: stats.mem_zfs_arc_gb,
|
||||
swap_total_gb: stats.swap_total_gb,
|
||||
swap_used_gb: stats.swap_used_gb,
|
||||
disk_total_gb,
|
||||
disk_used_gb,
|
||||
disks: stats.disks.clone(),
|
||||
|
||||
@@ -6,10 +6,7 @@ use komodo_client::entities::{
|
||||
container::ContainerListItem, image::ImageListItem,
|
||||
service::SwarmServiceListItem, stack::SwarmStackListItem,
|
||||
},
|
||||
stack::{
|
||||
Stack, StackService, StackServiceNames, StackServiceState,
|
||||
StackState,
|
||||
},
|
||||
stack::{Stack, StackService, StackServiceNames, StackState},
|
||||
swarm::SwarmState,
|
||||
};
|
||||
|
||||
@@ -98,18 +95,10 @@ pub async fn update_swarm_stack_cache(
|
||||
None,
|
||||
));
|
||||
|
||||
let state = swarm_service
|
||||
.as_ref()
|
||||
.map(|c| c.state.into())
|
||||
.unwrap_or(StackServiceState::Unknown);
|
||||
|
||||
StackService {
|
||||
stack_id: stack.id.clone(),
|
||||
stack_name: stack.name.clone(),
|
||||
service: service_name.clone(),
|
||||
container: None,
|
||||
swarm_service,
|
||||
state,
|
||||
image,
|
||||
image_digests,
|
||||
}
|
||||
@@ -187,19 +176,11 @@ pub async fn update_server_stack_cache(
|
||||
None
|
||||
));
|
||||
|
||||
let state = container
|
||||
.as_ref()
|
||||
.map(|c| c.state.into())
|
||||
.unwrap_or(StackServiceState::Unknown);
|
||||
|
||||
StackService {
|
||||
stack_id: stack.id.clone(),
|
||||
stack_name: stack.name.clone(),
|
||||
service: service_name.clone(),
|
||||
image: image.clone(),
|
||||
container,
|
||||
swarm_service: None,
|
||||
state,
|
||||
image_digests,
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
@@ -247,7 +228,7 @@ pub async fn update_swarm_deployment_cache(
|
||||
service
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| name == deployment.deployed_name())
|
||||
.map(|name| name == &deployment.name)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.cloned();
|
||||
@@ -301,7 +282,7 @@ pub async fn update_server_deployment_cache(
|
||||
for deployment in deployments {
|
||||
let container = containers
|
||||
.iter()
|
||||
.find(|container| container.name == deployment.deployed_name())
|
||||
.find(|container| container.name == deployment.name)
|
||||
.cloned();
|
||||
|
||||
let image_digests = container
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_timing_util::wait_until_timelength;
|
||||
@@ -17,7 +20,7 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
config::monitoring_interval,
|
||||
helpers::swarm::swarm_request,
|
||||
helpers::swarm::swarm_request_custom_timeout,
|
||||
monitor::{
|
||||
RefreshCacheResources,
|
||||
resources::{
|
||||
@@ -115,8 +118,12 @@ pub async fn refresh_swarm_cache(swarm: &Swarm, force: bool) {
|
||||
}
|
||||
|
||||
let PollSwarmStatusResponse { inspect, lists } =
|
||||
match swarm_request(&swarm.config.server_ids, PollSwarmStatus {})
|
||||
.await
|
||||
match swarm_request_custom_timeout(
|
||||
&swarm.config.server_ids,
|
||||
PollSwarmStatus {},
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(info) => info,
|
||||
Err(e) => {
|
||||
|
||||
@@ -85,6 +85,20 @@ impl PeripheryClient {
|
||||
&self,
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
{
|
||||
self
|
||||
.request_custom_timeout(request, Duration::from_secs(10))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn request_custom_timeout<T>(
|
||||
&self,
|
||||
request: T,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<T::Response>
|
||||
where
|
||||
T: std::fmt::Debug + Serialize + HasResponse,
|
||||
T::Response: DeserializeOwned,
|
||||
@@ -120,17 +134,11 @@ impl PeripheryClient {
|
||||
let res = async {
|
||||
// Poll for the associated response
|
||||
loop {
|
||||
let message = response_receiever
|
||||
.recv()
|
||||
// Periphery request handler sends pings every 4s
|
||||
// *on this channel specifically* so Core knows
|
||||
// request is being processed. Hardcoded 11s
|
||||
// allows for missed 5s ping due to network reconnect.
|
||||
.with_timeout(Duration::from_secs(10))
|
||||
.await?;
|
||||
let message =
|
||||
response_receiever.recv().with_timeout(timeout).await?;
|
||||
|
||||
// Still in progress, sent to avoid timeout.
|
||||
let Some(message) = message.decode()? else {
|
||||
// Just a ping from periphery request handler
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
+87
-200
@@ -1,10 +1,10 @@
|
||||
use std::{collections::HashMap, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::{
|
||||
bson::Document, mongo_indexed::doc, mungos::find::find_collect,
|
||||
};
|
||||
use futures_util::{TryStreamExt, future::BoxFuture};
|
||||
use futures_util::{FutureExt, future::BoxFuture};
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::{
|
||||
api::read::GetPermission,
|
||||
@@ -16,9 +16,7 @@ use komodo_client::{
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
permission::SpecificPermission,
|
||||
permission::{
|
||||
Permission, PermissionLevel, PermissionLevelAndSpecifics,
|
||||
},
|
||||
permission::{PermissionLevel, PermissionLevelAndSpecifics},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
resource::Resource,
|
||||
@@ -178,34 +176,14 @@ pub fn get_user_permission_on_resource<'a, T: KomodoResource>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Precomputed user permissions for listing resources of a type.
|
||||
/// Load with [load_list_permits], then check visibility of
|
||||
/// each resource with [ListPermits::permitted].
|
||||
pub enum ListPermits {
|
||||
/// The user can see all resources of the type,
|
||||
/// eg. admin, transparent mode, or 'all' access on the variant.
|
||||
Unrestricted,
|
||||
/// Visibility must be checked per-resource
|
||||
/// against the permissions table.
|
||||
Fine(Box<FineListPermits>),
|
||||
}
|
||||
|
||||
pub struct FineListPermits {
|
||||
required: PermissionLevelAndSpecifics,
|
||||
base: PermissionLevelAndSpecifics,
|
||||
permission_by_resource_id: HashMap<String, Permission>,
|
||||
additional_specific_cache:
|
||||
HashMap<ResourceTarget, IndexSet<SpecificPermission>>,
|
||||
user: User,
|
||||
}
|
||||
|
||||
pub async fn load_list_permits<T: KomodoResource>(
|
||||
pub async fn list_resources_for_user<T: KomodoResource>(
|
||||
filters: impl Into<Option<Document>>,
|
||||
user: &User,
|
||||
required: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<ListPermits> {
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
// Check admin
|
||||
if user.admin {
|
||||
return Ok(ListPermits::Unrestricted);
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
|
||||
let mut base = PermissionLevelAndSpecifics {
|
||||
@@ -218,8 +196,8 @@ pub async fn load_list_permits<T: KomodoResource>(
|
||||
};
|
||||
|
||||
// 'transparent_mode' early return.
|
||||
if base.fulfills(&required) {
|
||||
return Ok(ListPermits::Unrestricted);
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
|
||||
let resource_type = T::resource_type();
|
||||
@@ -228,8 +206,8 @@ pub async fn load_list_permits<T: KomodoResource>(
|
||||
if let Some(all_permission) = user.all.get(&resource_type) {
|
||||
base.elevate(all_permission);
|
||||
// 'user.all' early return.
|
||||
if base.fulfills(&required) {
|
||||
return Ok(ListPermits::Unrestricted);
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,23 +217,25 @@ pub async fn load_list_permits<T: KomodoResource>(
|
||||
if let Some(all_permission) = group.all.get(&resource_type) {
|
||||
base.elevate(all_permission);
|
||||
// 'group.all' early return.
|
||||
if base.fulfills(&required) {
|
||||
return Ok(ListPermits::Unrestricted);
|
||||
if base.fulfills(&permission) {
|
||||
return list_all_resources::<T>(filters).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pull any permissions on the variant using the permissions table
|
||||
let permissions = find_collect(
|
||||
&db_client().permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(&user.id, &groups)?,
|
||||
"resource_target.type": resource_type.as_ref(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query permissions on db")?;
|
||||
let (all, permissions) = tokio::try_join!(
|
||||
list_all_resources::<T>(filters),
|
||||
// And any ids using the permissions table
|
||||
find_collect(
|
||||
&db_client().permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(&user.id, &groups)?,
|
||||
"resource_target.type": resource_type.as_ref(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map(|res| res.context("failed to query permissions on db"))
|
||||
)?;
|
||||
|
||||
let permission_by_resource_id = permissions
|
||||
.into_iter()
|
||||
@@ -267,54 +247,34 @@ pub async fn load_list_permits<T: KomodoResource>(
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(ListPermits::Fine(
|
||||
FineListPermits {
|
||||
required,
|
||||
base,
|
||||
permission_by_resource_id,
|
||||
additional_specific_cache: Default::default(),
|
||||
user: user.clone(),
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
impl ListPermits {
|
||||
/// Check whether the user can see the given resource.
|
||||
pub async fn permitted<T: KomodoResource>(
|
||||
&mut self,
|
||||
resource: &Resource<T::Config, T::Info>,
|
||||
) -> anyhow::Result<bool> {
|
||||
let ListPermits::Fine(fine) = self else {
|
||||
return Ok(true);
|
||||
};
|
||||
let mut resources = Vec::new();
|
||||
let mut additional_specific_cache =
|
||||
HashMap::<ResourceTarget, IndexSet<SpecificPermission>>::new();
|
||||
|
||||
for resource in all {
|
||||
let mut perm = if let Some(perm) =
|
||||
fine.permission_by_resource_id.get(&resource.id)
|
||||
permission_by_resource_id.get(&resource.id)
|
||||
{
|
||||
fine.base.join(perm)
|
||||
base.join(perm)
|
||||
} else {
|
||||
fine.base.clone()
|
||||
base.clone()
|
||||
};
|
||||
// Add in the resource level base permissions,
|
||||
// matching [get_user_permission_on_resource].
|
||||
perm.elevate(&resource.base_permission);
|
||||
// Check if already fulfils
|
||||
if perm.fulfills(&fine.required) {
|
||||
return Ok(true);
|
||||
if perm.fulfills(&permission) {
|
||||
resources.push(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Also check if fulfills with inherited specific
|
||||
let additional_target = if let Some(additional_target) =
|
||||
T::inherit_specific_permissions_from(resource)
|
||||
T::inherit_specific_permissions_from(&resource)
|
||||
&& !additional_target.is_empty()
|
||||
{
|
||||
additional_target
|
||||
} else {
|
||||
return Ok(false);
|
||||
continue;
|
||||
};
|
||||
let additional_specific = match fine
|
||||
.additional_specific_cache
|
||||
let additional_specific = match additional_specific_cache
|
||||
.get(&additional_target)
|
||||
.cloned()
|
||||
{
|
||||
@@ -323,99 +283,21 @@ impl ListPermits {
|
||||
let specific = GetPermission {
|
||||
target: additional_target.clone(),
|
||||
}
|
||||
.resolve(&ReadArgs {
|
||||
user: fine.user.clone(),
|
||||
})
|
||||
.resolve(&ReadArgs { user: user.clone() })
|
||||
.await
|
||||
.map_err(|e| e.error)
|
||||
.context(
|
||||
"failed to get user permission on additional target",
|
||||
)?
|
||||
.specific;
|
||||
fine
|
||||
.additional_specific_cache
|
||||
additional_specific_cache
|
||||
.insert(additional_target, specific.clone());
|
||||
specific
|
||||
}
|
||||
};
|
||||
perm.specific.extend(additional_specific);
|
||||
Ok(perm.fulfills(&fine.required))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_resources_for_user<T: KomodoResource>(
|
||||
filters: impl Into<Option<Document>>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
let mut permits = load_list_permits::<T>(user, permission).await?;
|
||||
|
||||
if let ListPermits::Unrestricted = permits {
|
||||
return list_all_resources::<T>(filters, limit, skip).await;
|
||||
}
|
||||
|
||||
list_resources_with_permits::<T, _>(
|
||||
&mut permits,
|
||||
filters.into(),
|
||||
limit.into(),
|
||||
skip.into(),
|
||||
|resource| async move { Some(resource) },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Drive the cursor directly, checking each resource against the
|
||||
/// user permissions and the additional `filter` (eg. by state
|
||||
/// computed from the in memory caches), with limit / skip applied
|
||||
/// in memory after the filters. Stops pulling from the cursor as
|
||||
/// soon as the limit is reached, and avoids collecting resources
|
||||
/// the user cannot see.
|
||||
///
|
||||
/// `filter` receives each resource by value, and returns
|
||||
/// `Some(resource)` to keep it in the results, or `None` to drop it.
|
||||
pub async fn list_resources_with_permits<T: KomodoResource, F>(
|
||||
permits: &mut ListPermits,
|
||||
filters: Option<Document>,
|
||||
limit: Option<i64>,
|
||||
skip: Option<u64>,
|
||||
filter: impl Fn(Resource<T::Config, T::Info>) -> F,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>>
|
||||
where
|
||||
F: Future<Output = Option<Resource<T::Config, T::Info>>> + Send,
|
||||
{
|
||||
let mut cursor = T::coll()
|
||||
.find(filters.unwrap_or_default())
|
||||
.sort(doc! { "name": 1 })
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to query db for {}s", T::resource_type())
|
||||
})?;
|
||||
|
||||
let limit = limit.unwrap_or_default().max(0) as usize;
|
||||
let skip = skip.unwrap_or_default();
|
||||
let mut skipped = 0;
|
||||
let mut resources = Vec::new();
|
||||
|
||||
while let Some(resource) = cursor
|
||||
.try_next()
|
||||
.await
|
||||
.context("Failed to pull next resource from db cursor")?
|
||||
{
|
||||
if !permits.permitted::<T>(&resource).await? {
|
||||
continue;
|
||||
}
|
||||
let Some(resource) = filter(resource).await else {
|
||||
continue;
|
||||
};
|
||||
if skipped < skip {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
resources.push(resource);
|
||||
if limit != 0 && resources.len() >= limit {
|
||||
break;
|
||||
if perm.fulfills(&permission) {
|
||||
resources.push(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,28 +307,55 @@ where
|
||||
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
|
||||
pub async fn list_resource_ids_for_user<T: KomodoResource>(
|
||||
filters: Option<Document>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Option<Vec<String>>> {
|
||||
let mut permits = load_list_permits::<T>(user, permission).await?;
|
||||
|
||||
if let ListPermits::Unrestricted = permits {
|
||||
// Check admin
|
||||
if user.admin {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let ids = list_resources_with_permits::<T, _>(
|
||||
&mut permits,
|
||||
filters,
|
||||
limit.into(),
|
||||
skip.into(),
|
||||
|resource| async move { Some(resource) },
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| resource.id)
|
||||
.collect();
|
||||
let mut base = PermissionLevelAndSpecifics {
|
||||
level: if core_config().transparent_mode {
|
||||
PermissionLevel::Read
|
||||
} else {
|
||||
PermissionLevel::None
|
||||
},
|
||||
specific: Default::default(),
|
||||
};
|
||||
|
||||
// 'transparent_mode' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let resource_type = T::resource_type();
|
||||
|
||||
if let Some(all) = user.all.get(&resource_type) {
|
||||
base.elevate(all);
|
||||
// 'user.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Check user groups 'all' on variant
|
||||
let groups = get_user_user_groups(&user.id).await?;
|
||||
for group in &groups {
|
||||
if let Some(all) = group.all.get(&resource_type) {
|
||||
base.elevate(all);
|
||||
// 'group.all' early return.
|
||||
if base.fulfills(&permission) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ids = list_resources_for_user::<T>(filters, user, permission)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| resource.id)
|
||||
.collect();
|
||||
|
||||
Ok(Some(ids))
|
||||
}
|
||||
@@ -460,8 +369,6 @@ pub async fn user_resource_target_query(
|
||||
Ok(incoming_query)
|
||||
} else {
|
||||
let swarm_query = list_resource_ids_for_user::<Swarm>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -477,8 +384,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Swarm" });
|
||||
|
||||
let server_query = list_resource_ids_for_user::<Server>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -492,8 +397,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Server" });
|
||||
|
||||
let stack_query = list_resource_ids_for_user::<Stack>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -507,8 +410,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Stack" });
|
||||
|
||||
let deployment_query = list_resource_ids_for_user::<Deployment>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -522,8 +423,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
|
||||
|
||||
let build_query = list_resource_ids_for_user::<Build>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -537,8 +436,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Build" });
|
||||
|
||||
let repo_query = list_resource_ids_for_user::<Repo>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -552,8 +449,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Repo" });
|
||||
|
||||
let procedure_query = list_resource_ids_for_user::<Procedure>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -567,8 +462,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
let action_query = list_resource_ids_for_user::<Action>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -582,8 +475,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
|
||||
let builder_query = list_resource_ids_for_user::<Builder>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -597,8 +488,6 @@ pub async fn user_resource_target_query(
|
||||
.unwrap_or_else(|| doc! { "target.type": "Builder" });
|
||||
|
||||
let alerter_query = list_resource_ids_for_user::<Alerter>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
@@ -613,8 +502,6 @@ pub async fn user_resource_target_query(
|
||||
|
||||
let resource_sync_query =
|
||||
list_resource_ids_for_user::<ResourceSync>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user,
|
||||
PermissionLevel::Read.into(),
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_timing_util::{Timelength, wait_until_timelength};
|
||||
use komodo_client::entities::{
|
||||
ResourceTargetVariant, core_report::CoreReport,
|
||||
};
|
||||
use mogh_pki::{
|
||||
PkiKind, RotatableKeyPair, SpkiPublicKey,
|
||||
one_way::OneWayNoiseHandshake,
|
||||
};
|
||||
|
||||
use crate::{config::core_config, state::db_client};
|
||||
|
||||
pub fn spawn_reporting_loop() {
|
||||
let config = core_config();
|
||||
|
||||
if !config.reporting_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let keys = match RotatableKeyPair::from_private_key_spec(
|
||||
PkiKind::OneWay,
|
||||
&config.reporting_private_key,
|
||||
) {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to initialize reporting key pair. Reporting is disabled. {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let client = reqwest::Client::default();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
wait_until_timelength(Timelength::OneDay, 1100).await;
|
||||
if let Err(e) = report(&client, &keys).await {
|
||||
warn!("Reporting failed. {e:#}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn report(
|
||||
client: &reqwest::Client,
|
||||
keys: &RotatableKeyPair,
|
||||
) -> anyhow::Result<()> {
|
||||
let db = db_client();
|
||||
|
||||
let keys = keys.load();
|
||||
let private_key_bytes = keys
|
||||
.private
|
||||
.as_raw_bytes()
|
||||
.context("Invalid reporting private key.")?;
|
||||
|
||||
let endpoint_public_key = client
|
||||
.get("https://mogh.tech/report/public_key")
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to query for reporting endpoint public key for signature.")?
|
||||
.error_for_status()
|
||||
.context("Failed response for reporting endpoint public key for signature.")?
|
||||
.text()
|
||||
.await
|
||||
.context("Failed to get reporting endpoint public key for signature.")?;
|
||||
|
||||
let endpoint_public_key =
|
||||
SpkiPublicKey::maybe_pem_to_raw_bytes(&endpoint_public_key)
|
||||
.context("Invalid reporting endpoint public key.")?;
|
||||
|
||||
let users = db
|
||||
.users
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for users")?;
|
||||
let servers = db
|
||||
.servers
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for servers")?;
|
||||
let swarms = db
|
||||
.swarms
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for swarms")?;
|
||||
let stacks = db
|
||||
.stacks
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for stacks")?;
|
||||
let deployments = db
|
||||
.deployments
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for deployments")?;
|
||||
let builds = db
|
||||
.builds
|
||||
.count_documents(Default::default())
|
||||
.await
|
||||
.context("Failed to query database for builds")?;
|
||||
|
||||
let count = [
|
||||
(ResourceTargetVariant::Server, servers),
|
||||
(ResourceTargetVariant::Swarm, swarms),
|
||||
(ResourceTargetVariant::Stack, stacks),
|
||||
(ResourceTargetVariant::Deployment, deployments),
|
||||
(ResourceTargetVariant::Build, builds),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let report = CoreReport {
|
||||
report_public_key: keys.public().to_string(),
|
||||
users,
|
||||
count,
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&report)
|
||||
.context("Failed to serialize report JSON")?;
|
||||
|
||||
let timestamp =
|
||||
SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64;
|
||||
|
||||
let prologue =
|
||||
format!("POST|/report/komodo|{serialized}|{timestamp}");
|
||||
|
||||
let mut handshake = OneWayNoiseHandshake::new_initiator(
|
||||
&private_key_bytes,
|
||||
&endpoint_public_key,
|
||||
prologue.as_bytes(),
|
||||
)?;
|
||||
|
||||
let signature = handshake
|
||||
.generate_signature()
|
||||
.context("Failed to generate report signature")?;
|
||||
|
||||
client
|
||||
.post("https://mogh.tech/report/komodo")
|
||||
.header("x-api-signature", signature)
|
||||
.header("x-api-timestamp", timestamp)
|
||||
.header("content-type", "application/json")
|
||||
.body(serialized)
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to post report.")?
|
||||
.error_for_status()
|
||||
.context("Failed response for report post.")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -74,9 +74,8 @@ impl super::KomodoResource for Build {
|
||||
build.config.repo,
|
||||
build.config.branch,
|
||||
build.config.git_https,
|
||||
String::new(),
|
||||
);
|
||||
let (git_provider, repo, branch, git_https, linked_repo_name) =
|
||||
let (git_provider, repo, branch, git_https) =
|
||||
if build.config.linked_repo.is_empty() {
|
||||
default_git
|
||||
} else {
|
||||
@@ -90,7 +89,6 @@ impl super::KomodoResource for Build {
|
||||
r.config.repo.clone(),
|
||||
r.config.branch.clone(),
|
||||
r.config.git_https,
|
||||
r.name.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or(default_git)
|
||||
@@ -109,7 +107,6 @@ impl super::KomodoResource for Build {
|
||||
files_on_host: build.config.files_on_host,
|
||||
dockerfile_contents: !build.config.dockerfile.is_empty(),
|
||||
linked_repo: build.config.linked_repo,
|
||||
linked_repo_name,
|
||||
repo_link: repo_link(
|
||||
&git_provider,
|
||||
&repo,
|
||||
@@ -300,9 +297,7 @@ async fn validate_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The Build state as computed for the build list items,
|
||||
/// from the in memory action states / state cache.
|
||||
pub async fn get_build_state(id: &String) -> BuildState {
|
||||
async fn get_build_state(id: &String) -> BuildState {
|
||||
if action_states()
|
||||
.build
|
||||
.get(id)
|
||||
|
||||
@@ -54,7 +54,7 @@ impl super::KomodoResource for Builder {
|
||||
}
|
||||
BuilderConfig::Server(config) => (
|
||||
BuilderConfigVariant::Server.to_string(),
|
||||
Some(config.server_ids.join(", ")),
|
||||
Some(config.server_id),
|
||||
),
|
||||
BuilderConfig::Aws(config) => (
|
||||
BuilderConfigVariant::Aws.to_string(),
|
||||
@@ -184,11 +184,10 @@ async fn validate_config(
|
||||
config: &mut PartialBuilderConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
if let PartialBuilderConfig::Server(PartialServerBuilderConfig {
|
||||
server_ids: Some(server_ids),
|
||||
}) = config
|
||||
{
|
||||
for server_id in server_ids {
|
||||
match config {
|
||||
PartialBuilderConfig::Server(PartialServerBuilderConfig {
|
||||
server_id: Some(server_id),
|
||||
}) if !server_id.is_empty() => {
|
||||
let server = super::get_check_permissions::<Server>(
|
||||
server_id,
|
||||
user,
|
||||
@@ -197,6 +196,7 @@ async fn validate_config(
|
||||
.await?;
|
||||
*server_id = server.id;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use database::mungos::{
|
||||
by_id::update_one_by_id, mongodb::Collection, mongodb::bson::doc,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use database::mungos::mongodb::Collection;
|
||||
use formatting::format_serror;
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::entities::{
|
||||
@@ -13,7 +11,7 @@ use komodo_client::entities::{
|
||||
DeploymentListItemInfo, DeploymentQuerySpecifics,
|
||||
DeploymentState, PartialDeploymentConfig, conversions_from_str,
|
||||
},
|
||||
environment_vars_from_str, optional_string,
|
||||
environment_vars_from_str,
|
||||
permission::{
|
||||
PermissionLevel, PermissionLevelAndSpecifics, SpecificPermission,
|
||||
},
|
||||
@@ -25,8 +23,7 @@ use komodo_client::entities::{
|
||||
user::User,
|
||||
};
|
||||
use periphery_client::api::{
|
||||
container::{RemoveContainer, RenameContainer},
|
||||
swarm::RemoveSwarmServices,
|
||||
container::RemoveContainer, swarm::RemoveSwarmServices,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -37,10 +34,7 @@ use crate::{
|
||||
swarm::swarm_request,
|
||||
},
|
||||
monitor::{refresh_server_cache, refresh_swarm_cache},
|
||||
state::{
|
||||
action_states, all_resources_cache, db_client,
|
||||
deployment_status_cache,
|
||||
},
|
||||
state::{action_states, db_client, deployment_status_cache},
|
||||
};
|
||||
|
||||
use super::get_check_permissions;
|
||||
@@ -96,31 +90,31 @@ impl super::KomodoResource for Deployment {
|
||||
deployment: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let status = deployment_status_cache().get(&deployment.id).await;
|
||||
let state = get_deployment_state(&deployment.id)
|
||||
let state = if action_states()
|
||||
.deployment
|
||||
.get(&deployment.id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let all = all_resources_cache().load();
|
||||
let server_name = all
|
||||
.servers
|
||||
.get(&deployment.config.server_id)
|
||||
.map(|server| server.name.clone())
|
||||
.unwrap_or_default();
|
||||
let swarm_name = all
|
||||
.swarms
|
||||
.get(&deployment.config.swarm_id)
|
||||
.map(|swarm| swarm.name.clone())
|
||||
.unwrap_or_default();
|
||||
.map(|s| s.get().map(|s| s.deploying))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
DeploymentState::Deploying
|
||||
} else {
|
||||
status.as_ref().map(|s| s.curr.state).unwrap_or_default()
|
||||
};
|
||||
let (build_image, build_id) = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let (build_name, build_id, build_version) = all
|
||||
.builds
|
||||
.get(&build_id)
|
||||
.map(|b| (b.name.clone(), b.id.clone(), b.config.version))
|
||||
.unwrap_or((
|
||||
String::from("unknown"),
|
||||
String::new(),
|
||||
Default::default(),
|
||||
));
|
||||
let (build_name, build_id, build_version) =
|
||||
super::get::<Build>(&build_id)
|
||||
.await
|
||||
.map(|b| (b.name, b.id, b.config.version))
|
||||
.unwrap_or((
|
||||
String::from("unknown"),
|
||||
String::new(),
|
||||
Default::default(),
|
||||
));
|
||||
let version = if version.is_none() {
|
||||
build_version.to_string()
|
||||
} else {
|
||||
@@ -174,14 +168,10 @@ impl super::KomodoResource for Deployment {
|
||||
status: status.as_ref().and_then(|s| {
|
||||
s.curr.container.as_ref().and_then(|c| c.status.to_owned())
|
||||
}),
|
||||
custom_name: optional_string(deployment.info.deployed_name)
|
||||
.unwrap_or(deployment.config.custom_name),
|
||||
image,
|
||||
update_available,
|
||||
swarm_id: deployment.config.swarm_id,
|
||||
swarm_name,
|
||||
server_id: deployment.config.server_id,
|
||||
server_name,
|
||||
build_id,
|
||||
},
|
||||
}
|
||||
@@ -254,12 +244,11 @@ impl super::KomodoResource for Deployment {
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
id: &str,
|
||||
_id: &str,
|
||||
config: &mut Self::PartialConfig,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
validate_config(config, user).await?;
|
||||
handle_custom_name_update(id, config).await
|
||||
validate_config(config, user).await
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
@@ -325,7 +314,7 @@ impl super::KomodoResource for Deployment {
|
||||
SwarmOrServer::Swarm(swarm) => match swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
RemoveSwarmServices {
|
||||
services: vec![deployment.deployed_name().to_string()],
|
||||
services: vec![deployment.name.clone()],
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -363,7 +352,7 @@ impl super::KomodoResource for Deployment {
|
||||
};
|
||||
match periphery
|
||||
.request(RemoveContainer {
|
||||
name: deployment.deployed_name().to_string(),
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
@@ -447,100 +436,12 @@ async fn validate_config(
|
||||
environment_vars_from_str(environment)
|
||||
.context("Invalid environment")?;
|
||||
}
|
||||
if let Some(custom_name) = &config.custom_name
|
||||
&& !custom_name.trim().is_empty()
|
||||
{
|
||||
config.custom_name =
|
||||
Some(to_container_compatible_name(custom_name));
|
||||
}
|
||||
if let Some(extra_args) = &mut config.extra_args {
|
||||
extra_args.retain(|v| !empty_or_only_spaces(v))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles a config update which changes the custom container name:
|
||||
/// - Server mode: the container is renamed to keep the Deployment
|
||||
/// matched to it. This runs before the update is written to the
|
||||
/// database, so a failed rename rejects the update and nothing
|
||||
/// is left mismatched.
|
||||
/// - 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.
|
||||
async fn handle_custom_name_update(
|
||||
id: &str,
|
||||
config: &PartialDeploymentConfig,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(custom_name) = &config.custom_name else {
|
||||
return Ok(());
|
||||
};
|
||||
let deployment = super::get::<Deployment>(id).await?;
|
||||
// custom_name is already made container compatible by validate_config.
|
||||
let new_container_name = if custom_name.is_empty() {
|
||||
deployment.name.as_str()
|
||||
} else {
|
||||
custom_name.as_str()
|
||||
};
|
||||
if deployment.custom_name() == new_container_name {
|
||||
return Ok(());
|
||||
}
|
||||
if deployment.config.swarm_id.is_empty()
|
||||
&& !deployment.config.server_id.is_empty()
|
||||
{
|
||||
let container_state =
|
||||
get_deployment_state(&deployment.id).await?;
|
||||
if container_state == DeploymentState::Unknown {
|
||||
return Err(anyhow!(
|
||||
"Cannot change custom container name when container status is unknown"
|
||||
));
|
||||
}
|
||||
if container_state == DeploymentState::NotDeployed {
|
||||
return Ok(());
|
||||
}
|
||||
let server =
|
||||
super::get::<Server>(&deployment.config.server_id).await?;
|
||||
periphery_client(&server)
|
||||
.await?
|
||||
.request(RenameContainer {
|
||||
curr_name: deployment.deployed_name().to_string(),
|
||||
new_name: new_container_name.to_string(),
|
||||
})
|
||||
.await
|
||||
.context("Failed to rename container to the new custom name")?;
|
||||
// Keep the tracked deployed name matched to the renamed container.
|
||||
update_one_by_id(
|
||||
&db_client().deployments,
|
||||
&deployment.id,
|
||||
database::mungos::update::Update::Set(
|
||||
doc! { "info.deployed_name": new_container_name },
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
"Failed to update deployed name on db after container rename",
|
||||
)?;
|
||||
} else if !deployment.config.swarm_id.is_empty()
|
||||
&& deployment.info.deployed_name.is_empty()
|
||||
// Includes the Unknown state, in case a service
|
||||
// is still running under the current name.
|
||||
&& get_deployment_state(&deployment.id).await?
|
||||
!= DeploymentState::NotDeployed
|
||||
{
|
||||
update_one_by_id(
|
||||
&db_client().deployments,
|
||||
&deployment.id,
|
||||
database::mungos::update::Update::Set(
|
||||
doc! { "info.deployed_name": deployment.deployed_name() },
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("Failed to pin deployed service name on db")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_deployment_execution(
|
||||
deployment: &str,
|
||||
user: &User,
|
||||
|
||||
+41
-214
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::Future,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
@@ -18,7 +17,7 @@ use database::{
|
||||
},
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use futures_util::{TryStreamExt, future::join_all};
|
||||
use futures_util::future::join_all;
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::{
|
||||
api::{read::ExportResourcesToToml, write::CreateTag},
|
||||
@@ -71,8 +70,7 @@ pub use action::{
|
||||
refresh_action_state_cache, spawn_action_state_refresh_loop,
|
||||
};
|
||||
pub use build::{
|
||||
get_build_state, refresh_build_state_cache,
|
||||
spawn_build_state_refresh_loop,
|
||||
refresh_build_state_cache, spawn_build_state_refresh_loop,
|
||||
};
|
||||
pub use deployment::setup_deployment_execution;
|
||||
pub use procedure::{
|
||||
@@ -84,8 +82,7 @@ pub use refresh::{
|
||||
spawn_resource_refresh_loop,
|
||||
};
|
||||
pub use repo::{
|
||||
get_repo_state, refresh_repo_state_cache,
|
||||
spawn_repo_state_refresh_loop,
|
||||
refresh_repo_state_cache, spawn_repo_state_refresh_loop,
|
||||
};
|
||||
pub use server::{rotate_server_keys, update_server_public_key};
|
||||
|
||||
@@ -265,156 +262,20 @@ pub async fn get<T: KomodoResource>(
|
||||
/// Get full resource list with no permissions check.
|
||||
pub async fn list_all_resources<T: KomodoResource>(
|
||||
filters: impl Into<Option<Document>>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
let options = FindOptions::builder()
|
||||
.sort(doc! { "name": 1 })
|
||||
.limit(limit)
|
||||
.skip(skip);
|
||||
find_collect(T::coll(), filters, options.build())
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
find_collect(
|
||||
T::coll(),
|
||||
filters,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ListItemsQueryOptions<T: KomodoResource> {
|
||||
pub limit: u64,
|
||||
pub page: u64,
|
||||
pub sort_desc: bool,
|
||||
pub sort_by: ListItemSort<T::ListItem>,
|
||||
}
|
||||
|
||||
/// List item pagination for the `List<Resource>` apis, driving the
|
||||
/// mongo cursor directly instead of collecting the full resource
|
||||
/// list in memory. Each resource pulled from the cursor is checked
|
||||
/// against the user permissions and converted to its list item, and
|
||||
/// `filter` is applied before the item counts toward `limit` / `page`.
|
||||
/// This is required because some list item fields (eg. state,
|
||||
/// update available) are computed from in-memory caches rather than
|
||||
/// stored on the database, so they cannot be part of the db query.
|
||||
/// Stops pulling from the cursor as soon as the page is full.
|
||||
pub async fn list_items_for_user<T: KomodoResource>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
ListItemsQueryOptions {
|
||||
limit,
|
||||
page,
|
||||
sort_desc,
|
||||
sort_by,
|
||||
}: ListItemsQueryOptions<T>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
filter: impl Fn(&T::ListItem) -> bool,
|
||||
) -> anyhow::Result<Vec<T::ListItem>> {
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
|
||||
let mut permits =
|
||||
crate::permission::load_list_permits::<T>(user, permission)
|
||||
.await?;
|
||||
|
||||
let direction = if sort_desc { -1 } else { 1 };
|
||||
let sort = match &sort_by {
|
||||
ListItemSort::Name => doc! { "name": direction },
|
||||
// Db field sorts use ascending name as the secondary sort.
|
||||
ListItemSort::DbField(field) => {
|
||||
doc! { *field: direction, "name": 1 }
|
||||
}
|
||||
// In-memory sorts pull items in ascending name db order,
|
||||
// keeping name as the stable tiebreak for equal sort keys.
|
||||
ListItemSort::InMemory(_) => doc! { "name": 1 },
|
||||
};
|
||||
|
||||
let mut cursor =
|
||||
T::coll().find(filters).sort(sort).await.with_context(|| {
|
||||
format!("Failed to query db for {}s", T::resource_type())
|
||||
})?;
|
||||
|
||||
let skip = page.saturating_mul(limit) as usize;
|
||||
let take = if limit == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
limit as usize
|
||||
};
|
||||
let mut items = Vec::new();
|
||||
|
||||
if let ListItemSort::InMemory(compare) = sort_by {
|
||||
// The sort can only be applied after all matching items
|
||||
// are collected, so pagination also happens after the sort.
|
||||
while let Some(resource) = cursor
|
||||
.try_next()
|
||||
.await
|
||||
.context("Failed to pull next resource from db cursor")?
|
||||
{
|
||||
if !permits.permitted::<T>(&resource).await? {
|
||||
continue;
|
||||
}
|
||||
let item = T::to_list_item(resource).await;
|
||||
if !filter(&item) {
|
||||
continue;
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
// Stable sort keeps equal sort keys in ascending
|
||||
// name order for both directions.
|
||||
if sort_desc {
|
||||
items.sort_by(|a, b| compare(b, a));
|
||||
} else {
|
||||
items.sort_by(|a, b| compare(a, b));
|
||||
}
|
||||
Ok(items.into_iter().skip(skip).take(take).collect())
|
||||
} else {
|
||||
let mut skipped = 0;
|
||||
while let Some(resource) = cursor
|
||||
.try_next()
|
||||
.await
|
||||
.context("Failed to pull next resource from db cursor")?
|
||||
{
|
||||
if !permits.permitted::<T>(&resource).await? {
|
||||
continue;
|
||||
}
|
||||
let item = T::to_list_item(resource).await;
|
||||
if !filter(&item) {
|
||||
continue;
|
||||
}
|
||||
if skipped < skip {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
items.push(item);
|
||||
if take != usize::MAX && items.len() >= take {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
/// How the `List<Resource>` apis sort the list items.
|
||||
pub enum ListItemSort<I> {
|
||||
/// Sort by name at the db level. Default.
|
||||
Name,
|
||||
/// Sort on a db stored field, keeping db level sort
|
||||
/// and streaming pagination. Only usable when the db field
|
||||
/// exactly matches the displayed list item field.
|
||||
DbField(&'static str),
|
||||
/// Compare list items in memory, required for fields computed
|
||||
/// from the in memory caches (eg. state), or which diverge
|
||||
/// from the db field (eg. linked repo sources).
|
||||
/// Collects all matching items before applying pagination.
|
||||
InMemory(InMemoryListItemSortFn<I>),
|
||||
}
|
||||
|
||||
pub type InMemoryListItemSortFn<I> =
|
||||
Box<dyn Fn(&I, &I) -> std::cmp::Ordering + Send>;
|
||||
|
||||
pub async fn list_for_user<T: KomodoResource>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
@@ -422,25 +283,38 @@ pub async fn list_for_user<T: KomodoResource>(
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
list_for_user_using_document::<T>(
|
||||
filters, limit, skip, user, permission,
|
||||
)
|
||||
.await
|
||||
list_for_user_using_document::<T>(filters, user, permission).await
|
||||
}
|
||||
|
||||
// // pub async fn list_for_user_using_pattern<T: KomodoResource>(
|
||||
// pattern: &str,
|
||||
// query: ResourceQuery<T::QuerySpecifics>,
|
||||
// user: &User,
|
||||
// permissions: PermissionLevelAndSpecifics,
|
||||
// all_tags: &[Tag],
|
||||
// ) -> anyhow::Result<Vec<T::ListItem>> {
|
||||
// let list = list_full_for_user_using_pattern::<T>(
|
||||
// pattern,
|
||||
// query,
|
||||
// user,
|
||||
// permissions,
|
||||
// all_tags,
|
||||
// )
|
||||
// .await?
|
||||
// .into_iter()
|
||||
// .map(|resource| T::to_list_item(resource));
|
||||
// Ok(join_all(list).await)
|
||||
// }
|
||||
|
||||
pub async fn list_for_user_using_document<T: KomodoResource>(
|
||||
filters: Document,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
) -> anyhow::Result<Vec<T::ListItem>> {
|
||||
let list = list_resources_for_user::<T>(
|
||||
filters, limit, skip, user, permission,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| T::to_list_item(resource));
|
||||
let list = list_resources_for_user::<T>(filters, user, permission)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| T::to_list_item(resource));
|
||||
Ok(join_all(list).await)
|
||||
}
|
||||
|
||||
@@ -455,16 +329,13 @@ pub async fn list_for_user_using_document<T: KomodoResource>(
|
||||
pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
|
||||
pattern: &str,
|
||||
query: ResourceQuery<T::QuerySpecifics>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
let resources = list_full_for_user::<T>(
|
||||
query, limit, skip, user, permission, all_tags,
|
||||
)
|
||||
.await?;
|
||||
let resources =
|
||||
list_full_for_user::<T>(query, user, permission, all_tags)
|
||||
.await?;
|
||||
|
||||
let patterns = parse_string_list(pattern);
|
||||
let mut names = HashSet::<String>::new();
|
||||
@@ -497,45 +368,8 @@ pub async fn list_full_for_user_using_pattern<T: KomodoResource>(
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [list_full_for_user], but applies an additional in-memory
|
||||
/// `filter` (eg. by state computed from the in memory caches), with
|
||||
/// limit / page applied after the filter. Required because such
|
||||
/// filters cannot be expressed in the db level query.
|
||||
///
|
||||
/// `filter` receives each resource by value, and returns
|
||||
/// `Some(resource)` to keep it in the results, or `None` to drop it.
|
||||
pub async fn list_full_for_user_filtered<T: KomodoResource, F>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
limit: u64,
|
||||
page: u64,
|
||||
user: &User,
|
||||
permission: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
filter: impl Fn(Resource<T::Config, T::Info>) -> F,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>>
|
||||
where
|
||||
F: Future<Output = Option<Resource<T::Config, T::Info>>> + Send,
|
||||
{
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
let mut permits =
|
||||
crate::permission::load_list_permits::<T>(user, permission)
|
||||
.await?;
|
||||
crate::permission::list_resources_with_permits::<T, _>(
|
||||
&mut permits,
|
||||
Some(filters),
|
||||
Some(limit as i64),
|
||||
Some(page.saturating_mul(limit)),
|
||||
filter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_full_for_user<T: KomodoResource>(
|
||||
mut query: ResourceQuery<T::QuerySpecifics>,
|
||||
limit: impl Into<Option<i64>>,
|
||||
skip: impl Into<Option<u64>>,
|
||||
user: &User,
|
||||
permissions: PermissionLevelAndSpecifics,
|
||||
all_tags: &[Tag],
|
||||
@@ -543,14 +377,7 @@ pub async fn list_full_for_user<T: KomodoResource>(
|
||||
validate_resource_query_tags(&mut query, all_tags)?;
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
list_resources_for_user::<T>(
|
||||
filters,
|
||||
limit,
|
||||
skip,
|
||||
user,
|
||||
permissions,
|
||||
)
|
||||
.await
|
||||
list_resources_for_user::<T>(filters, user, permissions).await
|
||||
}
|
||||
|
||||
pub type IdResourceMap<T> = HashMap<
|
||||
|
||||
@@ -272,9 +272,6 @@ async fn validate_config(
|
||||
execute: [
|
||||
// Action
|
||||
(RunAction, Action, action),
|
||||
(CancelAction, Action, action),
|
||||
// Procedure
|
||||
(CancelProcedure, Procedure, procedure),
|
||||
// Build
|
||||
(RunBuild, Build, build),
|
||||
(CancelBuild, Build, build),
|
||||
|
||||
@@ -26,8 +26,7 @@ use crate::{
|
||||
config::core_config,
|
||||
helpers::{periphery_client, repo_link},
|
||||
state::{
|
||||
action_states, all_resources_cache, db_client, repo_state_cache,
|
||||
repo_status_cache,
|
||||
action_states, db_client, repo_state_cache, repo_status_cache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,12 +63,6 @@ impl super::KomodoResource for Repo {
|
||||
let state = get_repo_state(&repo.id).await;
|
||||
let status =
|
||||
repo_status_cache().get(&repo.id).await.unwrap_or_default();
|
||||
let server_name = all_resources_cache()
|
||||
.load()
|
||||
.servers
|
||||
.get(&repo.config.server_id)
|
||||
.map(|server| server.name.clone())
|
||||
.unwrap_or_default();
|
||||
RepoListItem {
|
||||
name: repo.name,
|
||||
id: repo.id,
|
||||
@@ -78,7 +71,6 @@ impl super::KomodoResource for Repo {
|
||||
resource_type: ResourceTargetVariant::Repo,
|
||||
info: RepoListItemInfo {
|
||||
server_id: repo.config.server_id,
|
||||
server_name,
|
||||
builder_id: repo.config.builder_id,
|
||||
last_pulled_at: repo.info.last_pulled_at,
|
||||
last_built_at: repo.info.last_built_at,
|
||||
@@ -271,9 +263,7 @@ async fn validate_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The Repo state as computed for the repo list items,
|
||||
/// from the in memory action states / state cache.
|
||||
pub async fn get_repo_state(id: &String) -> RepoState {
|
||||
async fn get_repo_state(id: &String) -> RepoState {
|
||||
if let Some(state) = action_states()
|
||||
.repo
|
||||
.get(id)
|
||||
|
||||
@@ -68,23 +68,14 @@ impl super::KomodoResource for Server {
|
||||
server: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let status = server_status_cache().get(&server.id).await;
|
||||
let (periphery_info, system_info, system_stats) = status
|
||||
.as_ref()
|
||||
.map(|s| {
|
||||
(
|
||||
s.periphery_info.as_ref(),
|
||||
s.system_info.as_ref(),
|
||||
s.system_stats.as_ref(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let (
|
||||
version,
|
||||
public_key,
|
||||
public_ip,
|
||||
terminals_disabled,
|
||||
container_terminals_disabled,
|
||||
) = match periphery_info {
|
||||
) = match status.as_ref().and_then(|s| s.periphery_info.as_ref())
|
||||
{
|
||||
Some(info) => (
|
||||
Some(info.version.clone()),
|
||||
Some(info.public_key.clone()),
|
||||
@@ -103,10 +94,6 @@ impl super::KomodoResource for Server {
|
||||
info: ServerListItemInfo {
|
||||
state: status.as_ref().map(|s| s.state).unwrap_or_default(),
|
||||
err: status.as_ref().and_then(|s| s.err.clone()),
|
||||
stats: system_stats.map(Into::into),
|
||||
core_count: system_info.and_then(|i| i.core_count),
|
||||
logical_core_count: system_info
|
||||
.and_then(|i| i.logical_core_count),
|
||||
region: server.config.region,
|
||||
address: optional_string(server.config.address),
|
||||
external_address: optional_string(
|
||||
|
||||
@@ -31,9 +31,7 @@ use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{
|
||||
get_cached_stack_state, get_stack_state, get_swarm_or_server,
|
||||
},
|
||||
query::{get_stack_state, get_swarm_or_server},
|
||||
repo_link,
|
||||
swarm::swarm_request,
|
||||
},
|
||||
@@ -97,7 +95,20 @@ impl super::KomodoResource for Stack {
|
||||
stack: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let status = stack_status_cache().get(&stack.id).await;
|
||||
let state = get_cached_stack_state(&stack.id).await;
|
||||
let state = if action_states()
|
||||
.stack
|
||||
.get(&stack.id)
|
||||
.await
|
||||
.map(|s| s.get().map(|s| s.deploying))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
StackState::Deploying
|
||||
} else {
|
||||
status.as_ref().map(|s| s.curr.state).unwrap_or_default()
|
||||
};
|
||||
let project_name = stack.project_name(false);
|
||||
let services = status
|
||||
.as_ref()
|
||||
@@ -145,9 +156,8 @@ impl super::KomodoResource for Stack {
|
||||
stack.config.repo,
|
||||
stack.config.branch,
|
||||
stack.config.git_https,
|
||||
String::new(),
|
||||
);
|
||||
let (git_provider, repo, branch, git_https, linked_repo_name) =
|
||||
let (git_provider, repo, branch, git_https) =
|
||||
if stack.config.linked_repo.is_empty() {
|
||||
default_git
|
||||
} else {
|
||||
@@ -161,7 +171,6 @@ impl super::KomodoResource for Stack {
|
||||
r.config.repo.clone(),
|
||||
r.config.branch.clone(),
|
||||
r.config.git_https,
|
||||
r.name.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or(default_git)
|
||||
@@ -196,18 +205,6 @@ impl super::KomodoResource for Stack {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
let all = all_resources_cache().load();
|
||||
let server_name = all
|
||||
.servers
|
||||
.get(&stack.config.server_id)
|
||||
.map(|server| server.name.clone())
|
||||
.unwrap_or_default();
|
||||
let swarm_name = all
|
||||
.swarms
|
||||
.get(&stack.config.swarm_id)
|
||||
.map(|swarm| swarm.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
StackListItem {
|
||||
name: stack.name,
|
||||
id: stack.id,
|
||||
@@ -221,11 +218,8 @@ impl super::KomodoResource for Stack {
|
||||
project_missing,
|
||||
file_contents: !stack.config.file_contents.is_empty(),
|
||||
swarm_id: stack.config.swarm_id,
|
||||
swarm_name,
|
||||
server_id: stack.config.server_id,
|
||||
server_name,
|
||||
linked_repo: stack.config.linked_repo,
|
||||
linked_repo_name,
|
||||
missing_files: stack.info.missing_files,
|
||||
files_on_host: stack.config.files_on_host,
|
||||
repo_link: repo_link(
|
||||
|
||||
@@ -57,9 +57,8 @@ impl super::KomodoResource for ResourceSync {
|
||||
resource_sync.config.repo,
|
||||
resource_sync.config.branch,
|
||||
resource_sync.config.git_https,
|
||||
String::new(),
|
||||
);
|
||||
let (git_provider, repo, branch, git_https, linked_repo_name) =
|
||||
let (git_provider, repo, branch, git_https) =
|
||||
if resource_sync.config.linked_repo.is_empty() {
|
||||
default_git
|
||||
} else {
|
||||
@@ -73,7 +72,6 @@ impl super::KomodoResource for ResourceSync {
|
||||
r.config.repo.clone(),
|
||||
r.config.branch.clone(),
|
||||
r.config.git_https,
|
||||
r.name.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or(default_git)
|
||||
@@ -90,7 +88,6 @@ impl super::KomodoResource for ResourceSync {
|
||||
files_on_host: resource_sync.config.files_on_host,
|
||||
managed: resource_sync.config.managed,
|
||||
linked_repo: resource_sync.config.linked_repo,
|
||||
linked_repo_name,
|
||||
repo_link: repo_link(
|
||||
&git_provider,
|
||||
&repo,
|
||||
|
||||
@@ -80,7 +80,7 @@ pub async fn execute_compose_with_stack_and_server<
|
||||
|
||||
// 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 = action_state.update(set_in_progress)?;
|
||||
let _action_guard = action_state.update(set_in_progress)?;
|
||||
|
||||
// Send update here for UI to recheck action state
|
||||
update_update(update.clone()).await?;
|
||||
@@ -105,10 +105,6 @@ pub async fn execute_compose_with_stack_and_server<
|
||||
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)
|
||||
|
||||
@@ -281,7 +281,7 @@ async fn ensure_first_server_and_builder() {
|
||||
name: name.to_string(),
|
||||
config: PartialBuilderConfig::Server(
|
||||
PartialServerBuilderConfig {
|
||||
server_ids: Some(vec![server.id]),
|
||||
server_id: Some(server.id),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
+1
-30
@@ -19,14 +19,13 @@ use komodo_client::entities::{
|
||||
swarm::SwarmState,
|
||||
};
|
||||
use mogh_cache::CloneCache;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
connection::PeripheryConnections,
|
||||
helpers::{
|
||||
action_state::ActionStates, all_resources::AllResourcesById,
|
||||
builder::BuilderUsage, image_digest::ImageDigestCache,
|
||||
image_digest::ImageDigestCache,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -209,7 +208,6 @@ pub fn action_state_cache() -> &'static ActionStateCache {
|
||||
ACTION_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// Store all resources in local cache for fast lookup
|
||||
pub fn all_resources_cache() -> &'static ArcSwap<AllResourcesById> {
|
||||
static ALL_RESOURCES: OnceLock<ArcSwap<AllResourcesById>> =
|
||||
OnceLock::new();
|
||||
@@ -224,30 +222,3 @@ pub fn image_digest_cache() -> &'static ImageDigestCache {
|
||||
OnceLock::new();
|
||||
IMAGE_DIGEST_CACHE.get_or_init(ImageDigestCache::new)
|
||||
}
|
||||
|
||||
/// Maps Builder id => downstream count map (eg server id => active count)
|
||||
type BuilderUsageCache = CloneCache<String, Arc<BuilderUsage>>;
|
||||
|
||||
/// For builders with multiple downstream machines to choose.
|
||||
/// Stores active build count for each downstream.
|
||||
/// Maps Builder id => downstream count map
|
||||
pub fn builder_usage_cache() -> &'static BuilderUsageCache {
|
||||
static BUILDER_USAGE_CACHE: OnceLock<BuilderUsageCache> =
|
||||
OnceLock::new();
|
||||
BUILDER_USAGE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
type CancelCache = CloneCache<String, CancellationToken>;
|
||||
|
||||
/// Maps procedure id => CancellationToken
|
||||
pub fn procedure_cancel_cache() -> &'static CancelCache {
|
||||
static PROCEDURE_CANCEL_CACHE: OnceLock<CancelCache> =
|
||||
OnceLock::new();
|
||||
PROCEDURE_CANCEL_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// Maps update id => CancellationToken
|
||||
pub fn action_cancel_cache() -> &'static CancelCache {
|
||||
static ACTION_CANCEL_CACHE: OnceLock<CancelCache> = OnceLock::new();
|
||||
ACTION_CANCEL_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
@@ -338,7 +338,9 @@ fn build_cache_for_deployment<'a>(
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig =
|
||||
deployment.config.clone().into();
|
||||
let config: PartialDeploymentConfig = config.into();
|
||||
let mut config: PartialDeploymentConfig = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut config);
|
||||
|
||||
let mut diff =
|
||||
Deployment::get_diff(original.config.clone(), config)?;
|
||||
@@ -570,7 +572,9 @@ fn build_cache_for_stack<'a>(
|
||||
// Merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: StackConfig = stack.config.clone().into();
|
||||
let config: PartialStackConfig = config.into();
|
||||
let mut config: PartialStackConfig = config.into();
|
||||
|
||||
Stack::validate_partial_config(&mut config);
|
||||
|
||||
let mut diff =
|
||||
Stack::get_diff(original.config.clone(), config)?;
|
||||
|
||||
@@ -74,7 +74,7 @@ pub async fn get_updates_for_execution<
|
||||
let config: Resource::Config = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
// Resource::validate_partial_config(&mut resource.config);
|
||||
Resource::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Resource::get_diff(
|
||||
original.config.clone(),
|
||||
|
||||
@@ -7,7 +7,6 @@ use komodo_client::entities::{
|
||||
tag::Tag,
|
||||
toml::{ResourceToml, ResourcesToml},
|
||||
};
|
||||
use partial_derive2::PartialDiff as _;
|
||||
use toml::ToToml;
|
||||
|
||||
use crate::resource::KomodoResource;
|
||||
@@ -16,7 +15,6 @@ pub mod deploy;
|
||||
pub mod execute;
|
||||
pub mod file;
|
||||
pub mod remote;
|
||||
pub mod replace_ids;
|
||||
pub mod resources;
|
||||
pub mod toml;
|
||||
pub mod user_groups;
|
||||
@@ -89,19 +87,16 @@ pub trait ResourceSyncTrait: ToToml + Sized {
|
||||
)
|
||||
}
|
||||
|
||||
// /// Apply any changes to incoming toml partial config
|
||||
// /// before it is diffed against existing config
|
||||
// fn validate_partial_config(_config: &mut Self::PartialConfig) {}
|
||||
/// Apply any changes to incoming toml partial config
|
||||
/// before it is diffed against existing config
|
||||
fn validate_partial_config(_config: &mut Self::PartialConfig) {}
|
||||
|
||||
/// Diffs the declared toml (partial) against the full existing config.
|
||||
/// Removes all fields from toml (partial) that haven't changed.
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Self::replace_ids(&mut original);
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
) -> anyhow::Result<Self::ConfigDiff>;
|
||||
|
||||
/// Apply any changes to computed config diff
|
||||
/// before logging
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
use komodo_client::entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helpers::procedure::replace_procedure_stage_ids_with_names,
|
||||
resource::KomodoResource, state::all_resources_cache,
|
||||
};
|
||||
|
||||
/// Trait implementing replacing of linked resource ids with names for Resource Sync TOML.
|
||||
pub trait ReplaceIds: KomodoResource {
|
||||
/// Replace linked ids (server_id, build_id, etc) with the resource name.
|
||||
fn replace_ids(_config: &mut Self::Config) {}
|
||||
}
|
||||
|
||||
// These have no linked resource ids to replace
|
||||
impl ReplaceIds for Server {}
|
||||
impl ReplaceIds for Action {}
|
||||
|
||||
impl ReplaceIds for ResourceSync {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Swarm {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
config.server_ids.iter_mut().for_each(|server_id| {
|
||||
*server_id = all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
});
|
||||
let mut res = Vec::with_capacity(config.server_ids.capacity());
|
||||
for server_id in &config.server_ids {
|
||||
res.push(
|
||||
all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Stack {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
config.swarm_id.clone_from(
|
||||
all
|
||||
.swarms
|
||||
.get(&config.swarm_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Deployment {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
config.swarm_id.clone_from(
|
||||
all
|
||||
.swarms
|
||||
.get(&config.swarm_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
if let DeploymentImage::Build { build_id, .. } = &mut config.image
|
||||
{
|
||||
build_id.clone_from(
|
||||
all
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| &b.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Build {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
config.builder_id.clone_from(
|
||||
all
|
||||
.builders
|
||||
.get(&config.builder_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Repo {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
let all = all_resources_cache().load();
|
||||
config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
config.builder_id.clone_from(
|
||||
all
|
||||
.builders
|
||||
.get(&config.builder_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Builder {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
if let BuilderConfig::Server(config) = config {
|
||||
let all = all_resources_cache().load();
|
||||
for server_id in &mut config.server_ids {
|
||||
*server_id = all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplaceIds for Procedure {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
replace_procedure_stage_ids_with_names(&mut config.stages);
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the inner ids of [ResourceTarget] variants with the
|
||||
/// referenced resource's name, looking each up in [AllResourcesById].
|
||||
/// The `System` variant carries no id and is a no-op.
|
||||
macro_rules! replace_resource_target_ids {
|
||||
($target:expr, $all:expr, { $( $variant:ident => $field:ident ),* $(,)? }) => {
|
||||
match $target {
|
||||
komodo_client::entities::ResourceTarget::System(_) => {}
|
||||
$(
|
||||
komodo_client::entities::ResourceTarget::$variant(id) => {
|
||||
*id = $all
|
||||
.$field
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ReplaceIds for Alerter {
|
||||
fn replace_ids(config: &mut Self::Config) {
|
||||
if config.resources.is_empty()
|
||||
&& config.except_resources.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
for resource in &mut config.resources {
|
||||
replace_resource_target_ids!(resource, all, {
|
||||
Swarm => swarms,
|
||||
Server => servers,
|
||||
Stack => stacks,
|
||||
Deployment => deployments,
|
||||
Build => builds,
|
||||
Repo => repos,
|
||||
Procedure => procedures,
|
||||
Action => actions,
|
||||
Builder => builders,
|
||||
Alerter => alerters,
|
||||
ResourceSync => syncs,
|
||||
});
|
||||
}
|
||||
for resource in &mut config.except_resources {
|
||||
replace_resource_target_ids!(resource, all, {
|
||||
Swarm => swarms,
|
||||
Server => servers,
|
||||
Stack => stacks,
|
||||
Deployment => deployments,
|
||||
Build => builds,
|
||||
Repo => repos,
|
||||
Procedure => procedures,
|
||||
Action => actions,
|
||||
Builder => builders,
|
||||
Alerter => alerters,
|
||||
ResourceSync => syncs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+214
-14
@@ -2,16 +2,28 @@ use std::collections::HashMap;
|
||||
|
||||
use formatting::{Color, bold, colored, muted};
|
||||
use komodo_client::entities::{
|
||||
ResourceTargetVariant, action::Action, alerter::Alerter,
|
||||
build::Build, builder::Builder, deployment::Deployment,
|
||||
procedure::Procedure, repo::Repo, server::Server, stack::Stack,
|
||||
swarm::Swarm, sync::ResourceSync, tag::Tag, update::Log,
|
||||
ResourceTargetVariant,
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
stack::Stack,
|
||||
swarm::Swarm,
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::Log,
|
||||
user::sync_user,
|
||||
};
|
||||
use partial_derive2::MaybeNone;
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
helpers::procedure::replace_procedure_stage_ids_with_names,
|
||||
resource::{KomodoResource, ResourceMetaUpdate},
|
||||
state::all_resources_cache,
|
||||
sync::{ToUpdateItem, execute::run_update_meta},
|
||||
};
|
||||
|
||||
@@ -21,19 +33,127 @@ use super::{
|
||||
include_resource_by_tags,
|
||||
};
|
||||
|
||||
impl ResourceSyncTrait for Server {}
|
||||
impl ResourceSyncTrait for Server {
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Server {}
|
||||
|
||||
impl ResourceSyncTrait for Swarm {}
|
||||
impl ResourceSyncTrait for Swarm {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
original.server_ids.iter_mut().for_each(|server_id| {
|
||||
*server_id = all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
});
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Swarm {}
|
||||
|
||||
impl ResourceSyncTrait for Deployment {}
|
||||
impl ResourceSyncTrait for Deployment {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
original.swarm_id = all
|
||||
.swarms
|
||||
.get(&original.swarm_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
original.server_id = all
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: all
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: *version,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Deployment {}
|
||||
|
||||
impl ResourceSyncTrait for Stack {}
|
||||
impl ResourceSyncTrait for Stack {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
original.swarm_id = all
|
||||
.swarms
|
||||
.get(&original.swarm_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
original.server_id = all
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
original.linked_repo = all
|
||||
.repos
|
||||
.get(&original.linked_repo)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Stack {}
|
||||
|
||||
impl ResourceSyncTrait for Build {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
original.builder_id = all
|
||||
.builders
|
||||
.get(&original.builder_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default();
|
||||
original.linked_repo = all
|
||||
.repos
|
||||
.get(&original.linked_repo)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
|
||||
fn validate_diff(diff: &mut Self::ConfigDiff) {
|
||||
if let Some((_, to)) = &diff.version {
|
||||
// When setting a build back to "latest" version,
|
||||
@@ -45,18 +165,76 @@ impl ResourceSyncTrait for Build {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Build {}
|
||||
|
||||
impl ResourceSyncTrait for Repo {}
|
||||
impl ResourceSyncTrait for Repo {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
// Need to replace server id with name
|
||||
original.server_id = all
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Need to replace builder id with name
|
||||
original.builder_id = all
|
||||
.builders
|
||||
.get(&original.builder_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Repo {}
|
||||
|
||||
impl ResourceSyncTrait for Alerter {}
|
||||
impl ResourceSyncTrait for Alerter {
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Alerter {}
|
||||
|
||||
impl ResourceSyncTrait for Builder {}
|
||||
impl ResourceSyncTrait for Builder {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace server builder id with name
|
||||
if let BuilderConfig::Server(config) = &mut original {
|
||||
let all = all_resources_cache().load();
|
||||
config.server_id = all
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Builder {}
|
||||
|
||||
impl ResourceSyncTrait for Action {}
|
||||
impl ResourceSyncTrait for Action {
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Action {}
|
||||
|
||||
impl ResourceSyncTrait for ResourceSync {
|
||||
@@ -138,11 +316,33 @@ impl ResourceSyncTrait for ResourceSync {
|
||||
// The sync must be files on host mode OR git repo mode
|
||||
(files_on_host || !config.repo.as_deref().unwrap_or_default().is_empty() || !config.linked_repo.as_deref().unwrap_or_default().is_empty())
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
let all = all_resources_cache().load();
|
||||
original.linked_repo = all
|
||||
.repos
|
||||
.get(&original.linked_repo)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for ResourceSync {}
|
||||
|
||||
impl ResourceSyncTrait for Procedure {}
|
||||
impl ResourceSyncTrait for Procedure {
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
replace_procedure_stage_ids_with_names(&mut original.stages);
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Procedure {
|
||||
async fn execute_sync_updates(
|
||||
|
||||
+164
-8
@@ -6,7 +6,7 @@ use komodo_client::entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, PartialBuilderConfig},
|
||||
builder::{Builder, BuilderConfig, PartialBuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
@@ -21,7 +21,8 @@ use komodo_client::entities::{
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
resource::KomodoResource, sync::replace_ids::ReplaceIds,
|
||||
helpers::procedure::replace_procedure_stage_ids_with_names,
|
||||
resource::KomodoResource, state::all_resources_cache,
|
||||
};
|
||||
|
||||
pub const TOML_PRETTY_OPTIONS: toml_pretty::Options =
|
||||
@@ -34,7 +35,11 @@ pub const TOML_PRETTY_OPTIONS: toml_pretty::Options =
|
||||
inline_array: false,
|
||||
};
|
||||
|
||||
pub trait ToToml: ReplaceIds {
|
||||
pub trait ToToml: KomodoResource {
|
||||
/// Replace linked ids (server_id, build_id, etc) with the resource name.
|
||||
fn replace_ids(_resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
_resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -103,7 +108,7 @@ pub fn resource_push_to_toml<R: ToToml>(
|
||||
toml: &mut String,
|
||||
all_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<()> {
|
||||
R::replace_ids(&mut resource.config);
|
||||
R::replace_ids(&mut resource);
|
||||
if !toml.is_empty() {
|
||||
toml.push_str("\n\n##\n\n");
|
||||
}
|
||||
@@ -152,12 +157,48 @@ pub fn convert_resource<R: KomodoResource>(
|
||||
}
|
||||
}
|
||||
|
||||
// These have no linked resource ids to replace
|
||||
impl ToToml for Alerter {}
|
||||
impl ToToml for Server {}
|
||||
impl ToToml for Action {}
|
||||
impl ToToml for Alerter {}
|
||||
impl ToToml for ResourceSync {}
|
||||
|
||||
impl ToToml for ResourceSync {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
resource.config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&resource.config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToToml for Swarm {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
resource.config.server_ids.iter_mut().for_each(|server_id| {
|
||||
*server_id = all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
});
|
||||
let mut res =
|
||||
Vec::with_capacity(resource.config.server_ids.capacity());
|
||||
for server_id in &resource.config.server_ids {
|
||||
res.push(
|
||||
all
|
||||
.servers
|
||||
.get(server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
_resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -179,6 +220,34 @@ impl ToToml for Swarm {
|
||||
}
|
||||
|
||||
impl ToToml for Stack {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
resource.config.swarm_id.clone_from(
|
||||
all
|
||||
.swarms
|
||||
.get(&resource.config.swarm_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
resource.config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&resource.config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
resource.config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&resource.config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
_resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -199,6 +268,38 @@ impl ToToml for Stack {
|
||||
}
|
||||
|
||||
impl ToToml for Deployment {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
|
||||
resource.config.swarm_id.clone_from(
|
||||
all
|
||||
.swarms
|
||||
.get(&resource.config.swarm_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
resource.config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&resource.config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
|
||||
if let DeploymentImage::Build { build_id, .. } =
|
||||
&mut resource.config.image
|
||||
{
|
||||
build_id.clone_from(
|
||||
all
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| &b.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -240,6 +341,24 @@ impl ToToml for Deployment {
|
||||
}
|
||||
|
||||
impl ToToml for Build {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
resource.config.builder_id.clone_from(
|
||||
all
|
||||
.builders
|
||||
.get(&resource.config.builder_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
resource.config.linked_repo.clone_from(
|
||||
all
|
||||
.repos
|
||||
.get(&resource.config.linked_repo)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -272,6 +391,24 @@ impl ToToml for Build {
|
||||
}
|
||||
|
||||
impl ToToml for Repo {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
let all = all_resources_cache().load();
|
||||
resource.config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&resource.config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
resource.config.builder_id.clone_from(
|
||||
all
|
||||
.builders
|
||||
.get(&resource.config.builder_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
|
||||
fn edit_config_object(
|
||||
_resource: &ResourceToml<Self::PartialConfig>,
|
||||
config: IndexMap<String, serde_json::Value>,
|
||||
@@ -293,6 +430,19 @@ impl ToToml for Repo {
|
||||
}
|
||||
|
||||
impl ToToml for Builder {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
if let BuilderConfig::Server(config) = &mut resource.config {
|
||||
let all = all_resources_cache().load();
|
||||
config.server_id.clone_from(
|
||||
all
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| &s.name)
|
||||
.unwrap_or(&String::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn push_additional(
|
||||
resource: ResourceToml<Self::PartialConfig>,
|
||||
toml: &mut String,
|
||||
@@ -326,8 +476,8 @@ impl ToToml for Builder {
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
match key.as_str() {
|
||||
"server_ids" => {
|
||||
return (String::from("servers"), value);
|
||||
"server_id" => {
|
||||
return (String::from("server"), value);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -346,6 +496,12 @@ impl ToToml for Builder {
|
||||
}
|
||||
|
||||
impl ToToml for Procedure {
|
||||
fn replace_ids(resource: &mut Resource<Self::Config, Self::Info>) {
|
||||
replace_procedure_stage_ids_with_names(
|
||||
&mut resource.config.stages,
|
||||
);
|
||||
}
|
||||
|
||||
fn push_to_toml_string(
|
||||
mut resource: ResourceToml<Self::PartialConfig>,
|
||||
toml: &mut String,
|
||||
|
||||
@@ -84,9 +84,9 @@ pub async fn push_updates_for_view<Resource: ResourceSyncTrait>(
|
||||
proposed_resource.config.into();
|
||||
proposed_resource.config = propsed_config.into();
|
||||
|
||||
// Resource::validate_partial_config(
|
||||
// &mut proposed_resource.config,
|
||||
// );
|
||||
Resource::validate_partial_config(
|
||||
&mut proposed_resource.config,
|
||||
);
|
||||
|
||||
let proposed = super::toml::resource_toml_to_toml_string::<
|
||||
Resource,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "komodo_periphery"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
@@ -33,7 +34,6 @@ git.workspace = true
|
||||
mogh_error = { workspace = true, features = ["axum"] }
|
||||
async_timing_util.workspace = true
|
||||
mogh_resolver.workspace = true
|
||||
svi.workspace = true
|
||||
# external
|
||||
hickory-resolver.workspace = true
|
||||
serde_yaml_ng.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
FROM rust:1.97.1-trixie AS builder
|
||||
FROM rust:1.95.0-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -5,13 +5,13 @@ use std::{
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
CommandOptions, KomodoCommandMode,
|
||||
run_komodo_command_with_sanitization, run_komodo_standard_command,
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
run_komodo_standard_command,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::entities::{
|
||||
EnvironmentVar, NoData, all_logs_success,
|
||||
EnvironmentVar, all_logs_success,
|
||||
build::{Build, BuildConfig},
|
||||
environment_vars_from_str, optional_string,
|
||||
to_path_compatible_name,
|
||||
@@ -19,19 +19,17 @@ use komodo_client::entities::{
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api::build::{
|
||||
self, CancelBuild, GetDockerfileContentsOnHost,
|
||||
self, GetDockerfileContentsOnHost,
|
||||
GetDockerfileContentsOnHostResponse, PruneBuilders, PruneBuildx,
|
||||
WriteDockerfileContentsToHost,
|
||||
};
|
||||
use tokio::fs;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
docker::docker_login,
|
||||
helpers::{format_extra_args, format_labels},
|
||||
state::build_cancel_cache,
|
||||
};
|
||||
|
||||
mod helpers;
|
||||
@@ -135,8 +133,7 @@ impl Resolve<crate::api::Args> for build::Build {
|
||||
fields(
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
build_id = self.build.id,
|
||||
build_name = self.build.name,
|
||||
build = self.build.name,
|
||||
repo = self.repo.as_ref().map(|repo| &repo.name),
|
||||
)
|
||||
)]
|
||||
@@ -266,21 +263,14 @@ impl Resolve<crate::api::Args> for build::Build {
|
||||
}
|
||||
};
|
||||
|
||||
let cancel = CancellationToken::new();
|
||||
build_cancel_cache()
|
||||
.insert(build.id.clone(), cancel.clone())
|
||||
.await;
|
||||
|
||||
// Pre Build
|
||||
if !pre_build.is_none() {
|
||||
let pre_build_path = build_path.join(&pre_build.path);
|
||||
let span = info_span!("RunPreBuild");
|
||||
if let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Pre Build",
|
||||
pre_build_path.as_path(),
|
||||
&pre_build.command,
|
||||
CommandOptions::default()
|
||||
.path(pre_build_path.as_path())
|
||||
.cancel(cancel.clone()),
|
||||
if pre_build.shell_mode {
|
||||
KomodoCommandMode::Shell
|
||||
} else {
|
||||
@@ -294,73 +284,53 @@ impl Resolve<crate::api::Args> for build::Build {
|
||||
let success = log.success;
|
||||
logs.push(log);
|
||||
if !success {
|
||||
build_cancel_cache().remove(&build.id).await;
|
||||
return Ok(logs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get command parts
|
||||
// Do this in fallible block to ensure
|
||||
// cancel token removed from cache on failure.
|
||||
let command = async {
|
||||
// Add VERSION to build args (if not already there)
|
||||
let mut build_args = environment_vars_from_str(build_args)
|
||||
.context("Invalid build_args")?;
|
||||
if !build_args.iter().any(|a| a.variable == "VERSION") {
|
||||
build_args.push(EnvironmentVar {
|
||||
variable: String::from("VERSION"),
|
||||
value: build.config.version.to_string(),
|
||||
});
|
||||
}
|
||||
let build_args = parse_build_args(&build_args);
|
||||
|
||||
let secret_args = environment_vars_from_str(secret_args)
|
||||
.context("Invalid secret_args")?;
|
||||
let command_secret_args =
|
||||
parse_secret_args(&secret_args, &build_path).await?;
|
||||
// Add VERSION to build args (if not already there)
|
||||
let mut build_args = environment_vars_from_str(build_args)
|
||||
.context("Invalid build_args")?;
|
||||
if !build_args.iter().any(|a| a.variable == "VERSION") {
|
||||
build_args.push(EnvironmentVar {
|
||||
variable: String::from("VERSION"),
|
||||
value: build.config.version.to_string(),
|
||||
});
|
||||
}
|
||||
let build_args = parse_build_args(&build_args);
|
||||
|
||||
let labels = format_labels(
|
||||
&environment_vars_from_str(labels)
|
||||
.context("Invalid labels")?,
|
||||
);
|
||||
let secret_args = environment_vars_from_str(secret_args)
|
||||
.context("Invalid secret_args")?;
|
||||
let command_secret_args =
|
||||
parse_secret_args(&secret_args, &build_path).await?;
|
||||
|
||||
let extra_args = format_extra_args(extra_args);
|
||||
let labels = format_labels(
|
||||
&environment_vars_from_str(labels).context("Invalid labels")?,
|
||||
);
|
||||
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
let extra_args = format_extra_args(extra_args);
|
||||
|
||||
let image_tags = build
|
||||
.get_image_tags_as_arg(
|
||||
commit_hash.as_deref(),
|
||||
&additional_tags,
|
||||
)
|
||||
.context("Failed to parse image tags into command")?;
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
|
||||
let maybe_push = if should_push { " --push" } else { "" };
|
||||
let image_tags = build
|
||||
.get_image_tags_as_arg(commit_hash.as_deref(), &additional_tags)
|
||||
.context("Failed to parse image tags into command")?;
|
||||
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"docker{buildx} build{build_args}{command_secret_args}{extra_args}{labels}{image_tags}{maybe_push} -f {dockerfile_path} .",
|
||||
);
|
||||
let maybe_push = if should_push { " --push" } else { "" };
|
||||
|
||||
anyhow::Ok(command)
|
||||
}.await;
|
||||
|
||||
let command = match command {
|
||||
Ok(command) => command,
|
||||
Err(e) => {
|
||||
build_cancel_cache().remove(&build.id).await;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"docker{buildx} build{build_args}{command_secret_args}{extra_args}{labels}{image_tags}{maybe_push} -f {dockerfile_path} .",
|
||||
);
|
||||
|
||||
let span = info_span!("RunDockerBuild");
|
||||
if let Some(build_log) = run_komodo_command_with_sanitization(
|
||||
"Docker Build",
|
||||
build_path.as_ref(),
|
||||
command,
|
||||
CommandOptions::default()
|
||||
.path(build_path.as_path())
|
||||
.cancel(cancel),
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
@@ -370,39 +340,12 @@ impl Resolve<crate::api::Args> for build::Build {
|
||||
logs.push(build_log);
|
||||
};
|
||||
|
||||
build_cancel_cache().remove(&build.id).await;
|
||||
|
||||
Ok(logs)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<crate::api::Args> for CancelBuild {
|
||||
#[instrument(
|
||||
"CancelBuild",
|
||||
skip_all,
|
||||
fields(
|
||||
build_id = self.id,
|
||||
id = args.id.to_string(),
|
||||
core = args.core,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
self,
|
||||
args: &crate::api::Args,
|
||||
) -> anyhow::Result<NoData> {
|
||||
build_cancel_cache()
|
||||
.get(&self.id)
|
||||
.await
|
||||
.context("No running build found")?
|
||||
.cancel();
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<crate::api::Args> for PruneBuilders {
|
||||
#[instrument(
|
||||
"PruneBuilders",
|
||||
@@ -418,12 +361,8 @@ impl Resolve<crate::api::Args> for PruneBuilders {
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker builder prune -a -f");
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Prune Builders",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Prune Builders", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -445,12 +384,8 @@ impl Resolve<crate::api::Args> for PruneBuildx {
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker buildx prune -a -f");
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Prune Buildx",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Prune Buildx", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::{
|
||||
borrow::Cow, fmt::Write, path::PathBuf, sync::LazyLock,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{borrow::Cow, fmt::Write, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
CommandOptions, KomodoCommandMode,
|
||||
run_komodo_command_with_sanitization, run_komodo_shell_command,
|
||||
run_komodo_standard_command,
|
||||
KomodoCommandMode, run_komodo_command_with_sanitization,
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use git::write_commit_file;
|
||||
@@ -15,7 +11,11 @@ use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
entities::{
|
||||
FileContents, RepoExecutionResponse, all_logs_success,
|
||||
stack::{AdditionalEnvFile, StackRemoteFileContents},
|
||||
stack::{
|
||||
AdditionalEnvFile, ComposeFile, ComposeService,
|
||||
ComposeServiceDeploy, StackRemoteFileContents,
|
||||
StackServiceNames,
|
||||
},
|
||||
to_path_compatible_name,
|
||||
update::Log,
|
||||
},
|
||||
@@ -28,7 +28,7 @@ use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
docker::compose::{docker_compose, parse_compose_services},
|
||||
docker::compose::docker_compose,
|
||||
helpers::{format_extra_args, format_log_grep},
|
||||
stack::{
|
||||
maybe_login_registry, pull_or_clone_stack, validate_files,
|
||||
@@ -54,16 +54,12 @@ impl Resolve<crate::api::Args> for GetComposeLog {
|
||||
Default::default()
|
||||
};
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project} logs --tail {tail}{timestamps} -- {}",
|
||||
"{docker_compose} -p {project} logs --tail {tail}{timestamps} {}",
|
||||
services.join(" ")
|
||||
);
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Get Stack Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Get Stack Log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -89,21 +85,12 @@ impl Resolve<crate::api::Args> for GetComposeLogSearch {
|
||||
Default::default()
|
||||
};
|
||||
let command = format!(
|
||||
"{docker_compose} -p {} logs --tail 5000{timestamps} -- {} 2>&1 | {grep}",
|
||||
escape(project.into()),
|
||||
services
|
||||
.iter()
|
||||
.map(|service| escape(service.into()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
"{docker_compose} -p {project} logs --tail 5000{timestamps} {} 2>&1 | {grep}",
|
||||
services.join(" ")
|
||||
);
|
||||
Ok(
|
||||
run_komodo_shell_command(
|
||||
"Search Stack Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
)
|
||||
.await,
|
||||
run_komodo_shell_command("Search Stack Log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -353,8 +340,7 @@ impl Resolve<crate::api::Args> for ComposePull {
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
// `--` so a service beginning with `-` is not parsed as a flag.
|
||||
format!(" -- {}", services.join(" "))
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
|
||||
let file_args = stack.compose_file_paths().join(" -f ");
|
||||
@@ -404,8 +390,8 @@ impl Resolve<crate::api::Args> for ComposePull {
|
||||
};
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Pull",
|
||||
run_directory.as_path(),
|
||||
pull_command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
mode,
|
||||
&replacers,
|
||||
)
|
||||
@@ -502,8 +488,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let span = info_span!("ExecutePreDeploy");
|
||||
if let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Pre Deploy",
|
||||
pre_deploy_path.as_path(),
|
||||
&stack.config.pre_deploy.command,
|
||||
CommandOptions::default().path(pre_deploy_path.as_path()),
|
||||
if stack.config.pre_deploy.shell_mode {
|
||||
KomodoCommandMode::Shell
|
||||
} else {
|
||||
@@ -526,8 +512,7 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
// `--` so a service beginning with `-` is not parsed as a flag.
|
||||
format!(" -- {}", services.join(" "))
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
|
||||
let file_args = stack.compose_file_paths().join(" -f ");
|
||||
@@ -574,55 +559,70 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
return Ok(res);
|
||||
}
|
||||
};
|
||||
let span = info_span!("GetComposeConfig", command);
|
||||
let mut config_log = if wrapped {
|
||||
run_komodo_shell_command(
|
||||
"Compose Config",
|
||||
command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
)
|
||||
.instrument(span)
|
||||
.await
|
||||
let mode = if wrapped {
|
||||
KomodoCommandMode::Shell
|
||||
} else {
|
||||
run_komodo_standard_command(
|
||||
"Compose Config",
|
||||
command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
)
|
||||
.instrument(span)
|
||||
.await
|
||||
KomodoCommandMode::Standard
|
||||
};
|
||||
let span = info_span!("GetComposeConfig", command);
|
||||
let Some(config_log) = run_komodo_command_with_sanitization(
|
||||
"Compose Config",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
mode,
|
||||
&replacers,
|
||||
)
|
||||
.instrument(span)
|
||||
.await
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if !config_log.success {
|
||||
config_log.sanitize(&replacers);
|
||||
res.logs.push(config_log);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
// attach sanitized merged config in any case.
|
||||
res.merged_config =
|
||||
svi::replace_in_string(&config_log.stdout, &replacers).into();
|
||||
|
||||
if let Err(e) = parse_compose_services(
|
||||
&config_log.stdout,
|
||||
&project_name,
|
||||
&mut res.services,
|
||||
) {
|
||||
config_log.sanitize(&replacers);
|
||||
res.logs.push(config_log);
|
||||
res.logs.push(Log::error(
|
||||
"Parse Compose Services",
|
||||
format_serror(&e.into()),
|
||||
));
|
||||
// early return with error log
|
||||
// including sanitized config log
|
||||
// and parse error for clear view
|
||||
// of what the issue might be.
|
||||
return Ok(res);
|
||||
let compose =
|
||||
serde_yaml_ng::from_str::<ComposeFile>(&config_log.stdout)
|
||||
.context("Failed to parse compose contents")?;
|
||||
// Store sanitized compose config output
|
||||
res.merged_config = Some(config_log.stdout);
|
||||
for (
|
||||
service_name,
|
||||
ComposeService {
|
||||
container_name,
|
||||
deploy,
|
||||
image,
|
||||
},
|
||||
) in compose.services
|
||||
{
|
||||
let image = image.unwrap_or_default();
|
||||
match deploy {
|
||||
Some(ComposeServiceDeploy {
|
||||
replicas: Some(replicas),
|
||||
}) if replicas > 1 => {
|
||||
for i in 1..1 + replicas {
|
||||
res.services.push(StackServiceNames {
|
||||
container_name: format!(
|
||||
"{project_name}-{service_name}-{i}"
|
||||
),
|
||||
service_name: format!("{service_name}-{i}"),
|
||||
image: image.clone(),
|
||||
image_digest: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
res.services.push(StackServiceNames {
|
||||
container_name: container_name.unwrap_or_else(|| {
|
||||
format!("{project_name}-{service_name}")
|
||||
}),
|
||||
service_name,
|
||||
image,
|
||||
image_digest: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_log.sanitize(&replacers);
|
||||
res.logs.push(config_log);
|
||||
}
|
||||
|
||||
if stack.config.run_build {
|
||||
@@ -651,8 +651,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let span = info_span!("ExecuteComposeBuild");
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Build",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
mode,
|
||||
&replacers,
|
||||
)
|
||||
@@ -694,8 +694,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let span = info_span!("RunComposePull");
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Pull",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
mode,
|
||||
&replacers,
|
||||
)
|
||||
@@ -742,8 +742,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let span = info_span!("ExecuteComposeUp");
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Up",
|
||||
run_directory.as_path(),
|
||||
command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
@@ -762,8 +762,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
|
||||
let span = info_span!("ExecutePostDeploy");
|
||||
if let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Post Deploy",
|
||||
post_deploy_path.as_path(),
|
||||
&stack.config.post_deploy.command,
|
||||
CommandOptions::default().path(post_deploy_path.as_path()),
|
||||
if stack.config.post_deploy.shell_mode {
|
||||
KomodoCommandMode::Shell
|
||||
} else {
|
||||
@@ -803,8 +803,8 @@ impl Resolve<crate::api::Args> for ComposeExecution {
|
||||
let docker_compose = docker_compose();
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Command",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} {command}"),
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
Ok(log)
|
||||
@@ -897,28 +897,23 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
|
||||
let project_name = stack.project_name(true);
|
||||
|
||||
// Comes straight off the request and interpolated into a
|
||||
// command string which is handed to `bash -c`, so must be escaped.
|
||||
let service = escape(Cow::Borrowed(&service));
|
||||
|
||||
// Parse wrapper configuration
|
||||
let compose_cmd_wrapper =
|
||||
parse_multiline_command(&stack.config.compose_cmd_wrapper);
|
||||
// If wrapper_include is empty but wrapper is set, use default ["up"] for backward compatibility
|
||||
static DEFAULT_INCLUDE: LazyLock<Vec<String>> =
|
||||
LazyLock::new(|| vec![String::from("up")]);
|
||||
let default_include = vec![String::from("up")];
|
||||
let wrapper_include =
|
||||
if stack.config.compose_cmd_wrapper_include.is_empty()
|
||||
&& !compose_cmd_wrapper.is_empty()
|
||||
{
|
||||
&DEFAULT_INCLUDE
|
||||
&default_include
|
||||
} else {
|
||||
&stack.config.compose_cmd_wrapper_include
|
||||
};
|
||||
|
||||
if pull.unwrap_or_default() {
|
||||
let pull_command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} pull -- {service}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} pull {service}",
|
||||
);
|
||||
let (pull_command, wrapped) = match maybe_wrap_command(
|
||||
pull_command,
|
||||
@@ -936,8 +931,8 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
};
|
||||
let Some(pull_log) = run_komodo_command_with_sanitization(
|
||||
"Compose Pull",
|
||||
run_directory.as_path(),
|
||||
pull_command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
mode,
|
||||
&replacers,
|
||||
)
|
||||
@@ -964,25 +959,17 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
run_flags.push_str(" --service-ports");
|
||||
}
|
||||
if let Some(dir) = workdir.as_ref() {
|
||||
run_flags
|
||||
.push_str(&format!(" --workdir {}", escape(dir.into())));
|
||||
run_flags.push_str(&format!(" --workdir {dir}"));
|
||||
}
|
||||
if let Some(user) = user.as_ref() {
|
||||
run_flags.push_str(&format!(" --user {}", escape(user.into())));
|
||||
run_flags.push_str(&format!(" --user {user}"));
|
||||
}
|
||||
if let Some(entrypoint) = entrypoint.as_ref() {
|
||||
run_flags.push_str(&format!(
|
||||
" --entrypoint {}",
|
||||
escape(entrypoint.into())
|
||||
));
|
||||
run_flags.push_str(&format!(" --entrypoint {entrypoint}"));
|
||||
}
|
||||
if let Some(env) = env {
|
||||
for (k, v) in env {
|
||||
// Escaped as a single KEY=VALUE token, which is what compose expects.
|
||||
run_flags.push_str(&format!(
|
||||
" -e {}",
|
||||
escape(format!("{k}={v}").into())
|
||||
));
|
||||
run_flags.push_str(&format!(" -e {}={} ", k, v));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +979,7 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
.map(|argv| {
|
||||
let joined = argv
|
||||
.iter()
|
||||
.map(|s| escape(s.into()).into_owned())
|
||||
.map(|s| escape(Cow::Borrowed(s)).into_owned())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!(" {joined}")
|
||||
@@ -1000,7 +987,7 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
.unwrap_or_default();
|
||||
|
||||
let run_command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} run{run_flags} -- {service}{command_args}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} run{run_flags} {service}{command_args}",
|
||||
);
|
||||
let (run_command, _) = match maybe_wrap_command(
|
||||
run_command,
|
||||
@@ -1015,8 +1002,8 @@ impl Resolve<crate::api::Args> for ComposeRun {
|
||||
let span = info_span!("RunComposeRun", run_command);
|
||||
let Some(log) = run_komodo_command_with_sanitization(
|
||||
"Compose Run",
|
||||
run_directory.as_path(),
|
||||
run_command,
|
||||
CommandOptions::default().path(run_directory.as_path()),
|
||||
KomodoCommandMode::Shell,
|
||||
&replacers,
|
||||
)
|
||||
@@ -1094,13 +1081,12 @@ async fn compose_down(
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
// `--` so a service beginning with `-` is not parsed as a flag.
|
||||
format!(" -- {}", services.join(" "))
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
let log = run_komodo_standard_command(
|
||||
"Compose Down",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} down{service_args}"),
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
let success = log.success;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use command::{
|
||||
CommandOptions, run_komodo_shell_command,
|
||||
run_komodo_standard_command,
|
||||
run_komodo_shell_command, run_komodo_standard_command,
|
||||
};
|
||||
use futures_util::future::join_all;
|
||||
use komodo_client::entities::{
|
||||
@@ -15,7 +12,6 @@ use komodo_client::entities::{
|
||||
};
|
||||
use mogh_resolver::Resolve;
|
||||
use periphery_client::api::container::*;
|
||||
use shell_escape::unix::escape;
|
||||
|
||||
use crate::{
|
||||
docker::{stats::get_container_stats, stop_container_command},
|
||||
@@ -62,16 +58,11 @@ impl Resolve<crate::api::Args> for GetContainerLog {
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
// `--` so a name beginning with `-` is not parsed as a flag.
|
||||
let command =
|
||||
format!("docker logs --tail {tail}{timestamps} -- {name}");
|
||||
format!("docker logs {name} --tail {tail}{timestamps}");
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Get container log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Get container log", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -96,15 +87,14 @@ impl Resolve<crate::api::Args> for GetContainerLogSearch {
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let name = escape(name.into());
|
||||
let command = format!(
|
||||
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
|
||||
);
|
||||
Ok(
|
||||
run_komodo_shell_command(
|
||||
"Get container log grep",
|
||||
None,
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -173,8 +163,8 @@ impl Resolve<crate::api::Args> for StartContainer {
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Docker Start",
|
||||
format!("docker start -- {}", self.name),
|
||||
CommandOptions::default(),
|
||||
None,
|
||||
format!("docker start {}", self.name),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -200,8 +190,8 @@ impl Resolve<crate::api::Args> for RestartContainer {
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Docker Restart",
|
||||
format!("docker restart -- {}", self.name),
|
||||
CommandOptions::default(),
|
||||
None,
|
||||
format!("docker restart {}", self.name),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -227,8 +217,8 @@ impl Resolve<crate::api::Args> for PauseContainer {
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Docker Pause",
|
||||
format!("docker pause -- {}", self.name),
|
||||
CommandOptions::default(),
|
||||
None,
|
||||
format!("docker pause {}", self.name),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -252,8 +242,8 @@ impl Resolve<crate::api::Args> for UnpauseContainer {
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Docker Unpause",
|
||||
format!("docker unpause -- {}", self.name),
|
||||
CommandOptions::default(),
|
||||
None,
|
||||
format!("docker unpause {}", self.name),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -278,20 +268,13 @@ impl Resolve<crate::api::Args> for StopContainer {
|
||||
) -> anyhow::Result<Log> {
|
||||
let StopContainer { name, signal, time } = self;
|
||||
let command = stop_container_command(&name, signal, time);
|
||||
let log = run_komodo_standard_command(
|
||||
"Docker Stop",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
let log =
|
||||
run_komodo_standard_command("Docker Stop", None, command).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(&name, None, time);
|
||||
let mut log = run_komodo_standard_command(
|
||||
"Docker Stop",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
let mut log =
|
||||
run_komodo_standard_command("Docker Stop", None, command)
|
||||
.await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -324,24 +307,23 @@ impl Resolve<crate::api::Args> for RemoveContainer {
|
||||
args: &crate::api::Args,
|
||||
) -> anyhow::Result<Log> {
|
||||
let RemoveContainer { name, signal, time } = self;
|
||||
let name = escape(name.into());
|
||||
let stop_command = stop_container_command(&name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm -- {name}");
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let log = run_komodo_shell_command(
|
||||
"Docker Stop and Remove",
|
||||
None,
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command = stop_container_command(&name, None, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm -- {name}");
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let mut log = run_komodo_shell_command(
|
||||
"Docker Stop and Remove",
|
||||
None,
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await;
|
||||
log.stderr = format!(
|
||||
@@ -380,14 +362,10 @@ impl Resolve<crate::api::Args> for RenameContainer {
|
||||
curr_name,
|
||||
new_name,
|
||||
} = self;
|
||||
let command = format!("docker rename -- {curr_name} {new_name}");
|
||||
let command = format!("docker rename {curr_name} {new_name}");
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Docker Rename",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Docker Rename", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -409,12 +387,8 @@ impl Resolve<crate::api::Args> for PruneContainers {
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker container prune -f");
|
||||
Ok(
|
||||
run_komodo_standard_command(
|
||||
"Prune Containers",
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await,
|
||||
run_komodo_standard_command("Prune Containers", None, command)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -450,14 +424,10 @@ impl Resolve<crate::api::Args> for StartAllContainers {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let command = format!("docker start -- {name}");
|
||||
let command = format!("docker start {name}");
|
||||
Some(async move {
|
||||
run_komodo_standard_command(
|
||||
&command.clone(),
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -496,14 +466,10 @@ impl Resolve<crate::api::Args> for RestartAllContainers {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let command = format!("docker restart -- {name}");
|
||||
let command = format!("docker restart {name}");
|
||||
Some(async move {
|
||||
run_komodo_standard_command(
|
||||
&command.clone(),
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -542,14 +508,10 @@ impl Resolve<crate::api::Args> for PauseAllContainers {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let command = format!("docker pause -- {name}");
|
||||
let command = format!("docker pause {name}");
|
||||
Some(async move {
|
||||
run_komodo_standard_command(
|
||||
&command.clone(),
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -588,14 +550,10 @@ impl Resolve<crate::api::Args> for UnpauseAllContainers {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let command = format!("docker unpause -- {name}");
|
||||
let command = format!("docker unpause {name}");
|
||||
Some(async move {
|
||||
run_komodo_standard_command(
|
||||
&command.clone(),
|
||||
command,
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await
|
||||
run_komodo_standard_command(&command.clone(), None, command)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -637,8 +595,8 @@ impl Resolve<crate::api::Args> for StopAllContainers {
|
||||
Some(async move {
|
||||
run_komodo_standard_command(
|
||||
&format!("Docker stop {name}"),
|
||||
None,
|
||||
stop_container_command(name, None, None),
|
||||
CommandOptions::default(),
|
||||
)
|
||||
.await
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user