forked from github-starred/komodo
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b8c89d976 | ||
|
|
25c8d25636 | ||
|
|
ea242de2e4 | ||
|
|
be03547407 | ||
|
|
9c0d28b311 | ||
|
|
f269deb99c | ||
|
|
3df8163131 | ||
|
|
33a16a9bd2 | ||
|
|
215e7d1bdc | ||
|
|
25e0905c0c | ||
|
|
1c07ccea85 | ||
|
|
405ec1b8cc | ||
|
|
4f212bd06f | ||
|
|
074f4ea2db | ||
|
|
c9abccaf02 | ||
|
|
6428fa6de2 | ||
|
|
883f54431d | ||
|
|
28dc030e2b | ||
|
|
145d933e63 | ||
|
|
9772ca1a1c | ||
|
|
4059b69201 | ||
|
|
8e175ea5a1 | ||
|
|
d931b8b4e7 | ||
|
|
0982800ad2 | ||
|
|
4382ad0b3b | ||
|
|
e7891f7870 | ||
|
|
6bada46841 | ||
|
|
eae6cbd228 | ||
|
|
a0ee6180b2 | ||
|
|
3ce3de8768 | ||
|
|
6c46993b61 | ||
|
|
fbd9d14aaa | ||
|
|
1011ec60ab | ||
|
|
48e17a7c87 | ||
|
|
a94baded55 | ||
|
|
e97c0873cf | ||
|
|
43a0b76811 | ||
|
|
2d2577e5ee | ||
|
|
202ac77de3 | ||
|
|
568c963419 | ||
|
|
5c3294241d | ||
|
|
648a04be88 | ||
|
|
1b5822f649 | ||
|
|
c41a008603 | ||
|
|
603243b0eb | ||
|
|
d09ab36696 | ||
|
|
ad168c87f7 | ||
|
|
914f4c6197 | ||
|
|
c73d918e18 | ||
|
|
9d116f56cb | ||
|
|
8a8dede5db | ||
|
|
d2cecf316c | ||
|
|
cad1ee123e | ||
|
|
6aa801b705 | ||
|
|
078ba59002 | ||
|
|
5eacb7191b | ||
|
|
45eafd10b9 | ||
|
|
42c486807c | ||
|
|
8c31fcff02 | ||
|
|
49f1d40ce8 | ||
|
|
bf85e886bd | ||
|
|
eda0b233ca | ||
|
|
5efb227851 | ||
|
|
1a45fffe75 | ||
|
|
fa72f2e5ef | ||
|
|
c9152db300 | ||
|
|
25fcca7246 | ||
|
|
ac449e38d5 | ||
|
|
d6c66948ba | ||
|
|
b6af790aef | ||
|
|
36a49210a0 | ||
|
|
d2b2aa0550 | ||
|
|
7f4c883416 | ||
|
|
676fb3c732 | ||
|
|
17da4bd2fa | ||
|
|
b44e57bbf6 | ||
|
|
6aa5b5faae | ||
|
|
9565855477 | ||
|
|
3504c083b4 | ||
|
|
5fdaa9a808 | ||
|
|
ec35b14077 | ||
|
|
158f3ad89b | ||
|
|
7257ecbaed | ||
|
|
a2a94f23ee | ||
|
|
03cad5b23b | ||
|
|
2460b5edf7 | ||
|
|
83fdb180aa | ||
|
|
9b1d32ebdf |
Generated
+96
-22
@@ -32,7 +32,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
@@ -921,6 +921,14 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -1413,6 +1421,17 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
@@ -1463,6 +1482,15 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@@ -1949,7 +1977,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2018,7 +2046,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2141,12 +2169,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"colored",
|
||||
"futures",
|
||||
"merge_config_files",
|
||||
"monitor_client",
|
||||
"partial_derive2",
|
||||
"serde",
|
||||
@@ -2160,7 +2189,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2183,7 +2212,7 @@ dependencies = [
|
||||
"strum 0.26.2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.22.0",
|
||||
"tokio-tungstenite 0.23.0",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"typeshare",
|
||||
@@ -2192,7 +2221,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2200,11 +2229,13 @@ dependencies = [
|
||||
"aws-sdk-ec2",
|
||||
"axum 0.7.5",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
"bcrypt",
|
||||
"derive_variants",
|
||||
"dotenv",
|
||||
"envy",
|
||||
"futures",
|
||||
"git",
|
||||
"hex",
|
||||
"hmac",
|
||||
"jwt",
|
||||
@@ -2213,6 +2244,7 @@ dependencies = [
|
||||
"mongo_indexed",
|
||||
"monitor_client",
|
||||
"mungos",
|
||||
"ordered_hash_map",
|
||||
"parse_csl",
|
||||
"partial_derive2",
|
||||
"periphery_client",
|
||||
@@ -2229,6 +2261,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"toml_pretty",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -2239,7 +2272,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2247,8 +2280,10 @@ dependencies = [
|
||||
"axum-extra",
|
||||
"bollard",
|
||||
"clap",
|
||||
"command",
|
||||
"dotenv",
|
||||
"envy",
|
||||
"git",
|
||||
"logger",
|
||||
"merge_config_files",
|
||||
"monitor_client",
|
||||
@@ -2485,6 +2520,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered_hash_map"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.5.1"
|
||||
@@ -2528,18 +2573,18 @@ checksum = "ffa94c2e5674923c67d7f3dfce1279507b191e10eb064881b46ed3e1256e5ca6"
|
||||
|
||||
[[package]]
|
||||
name = "partial_derive2"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7b915bd76bc306b7ae6f1b5d99b0434e498ab979ecbd5df119db8a00dab972"
|
||||
checksum = "6b2bd06fda40521c285c8dfc5f6b90208d586328a295e83332b6166c2b2a4241"
|
||||
dependencies = [
|
||||
"partial_derive2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "partial_derive2_derive"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddaac49c0e65bcb207999d2514b10b43d5f2ec2d0fb47b9d875c9a10536a294e"
|
||||
checksum = "3a506f66d52e40b2385d7b9f776fd5243d6cff16ba79147f859aa4e27d2d27cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2563,7 +2608,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -3194,13 +3239,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serror"
|
||||
version = "0.3.5"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c2d61f8b059a9ac51ab4f65ea9874cb1b8a6e57f2ebc6d284988f6b877253b"
|
||||
checksum = "a58297fe0d139a2950d2f474c3f5d0614eecc2b90f3bfe3ee28dca36dfcfd78d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
"axum-extra",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -3541,7 +3585,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenv",
|
||||
@@ -3713,19 +3757,19 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
"tungstenite 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d46baf930138837d65e25e3b33be49c9228579a6135dbf756b5cb9e4283e7cef"
|
||||
checksum = "becd34a233e7e31a3dbf7c7241b38320f57393dcae8e7324b0167d21b8e320b0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
"tungstenite 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3776,6 +3820,18 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_pretty"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "100b9fea6caaa91d9e7837591cbda2958b6ff16318b13c8ba92254fdda13db47"
|
||||
dependencies = [
|
||||
"ordered_hash_map",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.11.0"
|
||||
@@ -4019,6 +4075,24 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.10.0"
|
||||
@@ -4096,7 +4170,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
|
||||
+13
-8
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,23 +15,26 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.6.0"
|
||||
monitor_client = "1.7.3"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
command = { path = "lib/command" }
|
||||
logger = { path = "lib/logger" }
|
||||
git = { path = "lib/git" }
|
||||
|
||||
# MOGH
|
||||
run_command = { version = "0.0.6", features = ["async_tokio"] }
|
||||
serror = { version = "0.3.5", default-features = false }
|
||||
serror = { version = "0.4.3", default-features = false }
|
||||
slack = { version = "0.1.0", package = "slack_client_rs" }
|
||||
derive_default_builder = "0.1.8"
|
||||
derive_empty_traits = "0.1.0"
|
||||
merge_config_files = "0.1.5"
|
||||
termination_signal = "0.1.3"
|
||||
async_timing_util = "0.1.14"
|
||||
partial_derive2 = "0.4.2"
|
||||
partial_derive2 = "0.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "0.3.0"
|
||||
resolver_api = "1.1.0"
|
||||
toml_pretty = "1.1.1"
|
||||
parse_csl = "0.1.0"
|
||||
mungos = "0.5.6"
|
||||
svi = "1.0.1"
|
||||
@@ -48,9 +51,10 @@ axum = { version = "0.7.5", features = ["ws", "json"] }
|
||||
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
||||
tower = { version = "0.4.13", features = ["timeout"] }
|
||||
tower-http = { version = "0.5.2", features = ["fs", "cors"] }
|
||||
tokio-tungstenite = "0.22.0"
|
||||
tokio-tungstenite = "0.23.0"
|
||||
|
||||
# SER/DE
|
||||
ordered_hash_map = { version = "0.4.0", features = ["serde"] }
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
strum = { version = "0.26.2", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
@@ -76,11 +80,12 @@ envy = "0.4.2"
|
||||
# CRYPTO
|
||||
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
rand = "0.8.5"
|
||||
jwt = "0.16.0"
|
||||
bcrypt = "0.15.1"
|
||||
base64 = "0.22.1"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.8"
|
||||
bcrypt = "0.15.1"
|
||||
rand = "0.8.5"
|
||||
jwt = "0.16.0"
|
||||
hex = "0.4.3"
|
||||
|
||||
# SYSTEM
|
||||
|
||||
+2
-1
@@ -15,12 +15,13 @@ path = "src/main.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# local client
|
||||
# local
|
||||
monitor_client.workspace = true
|
||||
# mogh
|
||||
partial_derive2.workspace = true
|
||||
# external
|
||||
tracing-subscriber.workspace = true
|
||||
merge_config_files.workspace = true
|
||||
serde_json.workspace = true
|
||||
futures.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ pub struct CliArgs {
|
||||
pub command: Command,
|
||||
|
||||
/// The path to a creds file.
|
||||
///
|
||||
///
|
||||
/// Note: If each of `url`, `key` and `secret` are passed,
|
||||
/// no file is required at this path.
|
||||
#[arg(long, default_value_t = default_creds())]
|
||||
|
||||
@@ -57,6 +57,12 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> {
|
||||
Execution::PruneContainers(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
Execution::RunSync(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
Execution::Sleep(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
}
|
||||
|
||||
if !cli_args().yes {
|
||||
@@ -102,6 +108,16 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> {
|
||||
Execution::PruneContainers(request) => {
|
||||
monitor_client().execute(request).await
|
||||
}
|
||||
Execution::RunSync(request) => {
|
||||
monitor_client().execute(request).await
|
||||
}
|
||||
Execution::Sleep(request) => {
|
||||
let duration =
|
||||
Duration::from_millis(request.duration_ms as u64);
|
||||
tokio::time::sleep(duration).await;
|
||||
println!("Finished sleeping!");
|
||||
std::process::exit(0)
|
||||
}
|
||||
Execution::None(_) => unreachable!(),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,15 +2,6 @@ use std::io::Read;
|
||||
|
||||
use anyhow::Context;
|
||||
use colored::Colorize;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub fn parse_toml_file<T: DeserializeOwned>(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> anyhow::Result<T> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.context("failed to read file contents")?;
|
||||
toml::from_str(&contents).context("failed to parse toml contents")
|
||||
}
|
||||
|
||||
pub fn wait_for_enter(press_enter_to: &str) -> anyhow::Result<()> {
|
||||
println!(
|
||||
|
||||
+33
-2
@@ -5,8 +5,9 @@ use monitor_client::{
|
||||
entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate, tag::Tag,
|
||||
user::User, user_group::UserGroup, variable::Variable,
|
||||
server::Server, server_template::ServerTemplate,
|
||||
sync::ResourceSync, tag::Tag, user::User, user_group::UserGroup,
|
||||
variable::Variable,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -240,6 +241,36 @@ pub fn id_to_server_template(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name_to_resource_sync(
|
||||
) -> &'static HashMap<String, ResourceSync> {
|
||||
static NAME_TO_SYNC: OnceLock<HashMap<String, ResourceSync>> =
|
||||
OnceLock::new();
|
||||
NAME_TO_SYNC.get_or_init(|| {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullResourceSyncs::default()),
|
||||
)
|
||||
.expect("failed to get syncs from monitor")
|
||||
.into_iter()
|
||||
.map(|sync| (sync.name.clone(), sync))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id_to_resource_sync() -> &'static HashMap<String, ResourceSync>
|
||||
{
|
||||
static ID_TO_SYNC: OnceLock<HashMap<String, ResourceSync>> =
|
||||
OnceLock::new();
|
||||
ID_TO_SYNC.get_or_init(|| {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullResourceSyncs::default()),
|
||||
)
|
||||
.expect("failed to get syncs from monitor")
|
||||
.into_iter()
|
||||
.map(|sync| (sync.id.clone(), sync))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name_to_user_group() -> &'static HashMap<String, UserGroup> {
|
||||
static NAME_TO_USER_GROUP: OnceLock<HashMap<String, UserGroup>> =
|
||||
OnceLock::new();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clap::Parser;
|
||||
use merge_config_files::parse_config_file;
|
||||
use monitor_client::MonitorClient;
|
||||
|
||||
pub fn cli_args() -> &'static crate::args::CliArgs {
|
||||
@@ -23,7 +24,7 @@ pub fn monitor_client() -> &'static MonitorClient {
|
||||
}
|
||||
(url, key, secret) => {
|
||||
let mut creds: crate::args::CredsFile =
|
||||
crate::helpers::parse_toml_file(&cli_args().creds)
|
||||
parse_config_file(cli_args().creds.as_str())
|
||||
.expect("failed to parse monitor credentials");
|
||||
|
||||
if let Some(url) = url {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
use anyhow::{anyhow, Context};
|
||||
use colored::Colorize;
|
||||
use monitor_client::entities::toml::ResourcesToml;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub fn read_resources(path: &str) -> anyhow::Result<ResourcesToml> {
|
||||
let mut res = ResourcesToml::default();
|
||||
@@ -30,14 +31,13 @@ fn read_resources_recursive(
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let more =
|
||||
match crate::helpers::parse_toml_file::<ResourcesToml>(path) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("failed to parse {:?}. skipping file | {e:#}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let more = match parse_toml_file::<ResourcesToml>(path) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("failed to parse {:?}. skipping file | {e:#}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"{} from {}",
|
||||
"adding resources".green().bold(),
|
||||
@@ -45,12 +45,13 @@ fn read_resources_recursive(
|
||||
);
|
||||
resources.servers.extend(more.servers);
|
||||
resources.deployments.extend(more.deployments);
|
||||
resources.repos.extend(more.repos);
|
||||
resources.builds.extend(more.builds);
|
||||
resources.repos.extend(more.repos);
|
||||
resources.procedures.extend(more.procedures);
|
||||
resources.builders.extend(more.builders);
|
||||
resources.alerters.extend(more.alerters);
|
||||
resources.server_templates.extend(more.server_templates);
|
||||
resources.resource_syncs.extend(more.resource_syncs);
|
||||
resources.user_groups.extend(more.user_groups);
|
||||
resources.variables.extend(more.variables);
|
||||
Ok(())
|
||||
@@ -69,3 +70,11 @@ fn read_resources_recursive(
|
||||
Err(anyhow!("resources path is neither file nor directory"))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_toml_file<T: DeserializeOwned>(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> anyhow::Result<T> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.context("failed to read file contents")?;
|
||||
toml::from_str(&contents).context("failed to parse toml contents")
|
||||
}
|
||||
|
||||
+42
-16
@@ -1,10 +1,9 @@
|
||||
use colored::Colorize;
|
||||
use monitor_client::entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
self, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate,
|
||||
};
|
||||
use resource::ResourceSync;
|
||||
|
||||
use crate::{helpers::wait_for_enter, state::cli_args};
|
||||
|
||||
@@ -14,6 +13,8 @@ mod resources;
|
||||
mod user_group;
|
||||
mod variables;
|
||||
|
||||
use resource::ResourceSync;
|
||||
|
||||
pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
info!("resources path: {}", path.blue().bold());
|
||||
if delete {
|
||||
@@ -24,14 +25,6 @@ pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
|
||||
info!("computing sync actions...");
|
||||
|
||||
let (
|
||||
server_template_creates,
|
||||
server_template_updates,
|
||||
server_template_deletes,
|
||||
) = resource::get_updates::<ServerTemplate>(
|
||||
resources.server_templates,
|
||||
delete,
|
||||
)?;
|
||||
let (server_creates, server_updates, server_deletes) =
|
||||
resource::get_updates::<Server>(resources.servers, delete)?;
|
||||
let (deployment_creates, deployment_updates, deployment_deletes) =
|
||||
@@ -41,14 +34,30 @@ pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
)?;
|
||||
let (build_creates, build_updates, build_deletes) =
|
||||
resource::get_updates::<Build>(resources.builds, delete)?;
|
||||
let (builder_creates, builder_updates, builder_deletes) =
|
||||
resource::get_updates::<Builder>(resources.builders, delete)?;
|
||||
let (alerter_creates, alerter_updates, alerter_deletes) =
|
||||
resource::get_updates::<Alerter>(resources.alerters, delete)?;
|
||||
let (repo_creates, repo_updates, repo_deletes) =
|
||||
resource::get_updates::<Repo>(resources.repos, delete)?;
|
||||
let (procedure_creates, procedure_updates, procedure_deletes) =
|
||||
resource::get_updates::<Procedure>(resources.procedures, delete)?;
|
||||
let (builder_creates, builder_updates, builder_deletes) =
|
||||
resource::get_updates::<Builder>(resources.builders, delete)?;
|
||||
let (alerter_creates, alerter_updates, alerter_deletes) =
|
||||
resource::get_updates::<Alerter>(resources.alerters, delete)?;
|
||||
let (
|
||||
server_template_creates,
|
||||
server_template_updates,
|
||||
server_template_deletes,
|
||||
) = resource::get_updates::<ServerTemplate>(
|
||||
resources.server_templates,
|
||||
delete,
|
||||
)?;
|
||||
let (
|
||||
resource_sync_creates,
|
||||
resource_sync_updates,
|
||||
resource_sync_deletes,
|
||||
) = resource::get_updates::<entities::sync::ResourceSync>(
|
||||
resources.resource_syncs,
|
||||
delete,
|
||||
)?;
|
||||
|
||||
let (variable_creates, variable_updates, variable_deletes) =
|
||||
variables::get_updates(resources.variables, delete)?;
|
||||
@@ -56,22 +65,33 @@ pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
let (user_group_creates, user_group_updates, user_group_deletes) =
|
||||
user_group::get_updates(resources.user_groups, delete).await?;
|
||||
|
||||
if server_template_creates.is_empty()
|
||||
if resource_sync_creates.is_empty()
|
||||
&& resource_sync_updates.is_empty()
|
||||
&& resource_sync_deletes.is_empty()
|
||||
&& server_template_creates.is_empty()
|
||||
&& server_template_updates.is_empty()
|
||||
&& server_template_deletes.is_empty()
|
||||
&& server_creates.is_empty()
|
||||
&& server_updates.is_empty()
|
||||
&& server_deletes.is_empty()
|
||||
&& deployment_creates.is_empty()
|
||||
&& deployment_updates.is_empty()
|
||||
&& deployment_deletes.is_empty()
|
||||
&& build_creates.is_empty()
|
||||
&& build_updates.is_empty()
|
||||
&& build_deletes.is_empty()
|
||||
&& builder_creates.is_empty()
|
||||
&& builder_updates.is_empty()
|
||||
&& builder_deletes.is_empty()
|
||||
&& alerter_creates.is_empty()
|
||||
&& alerter_updates.is_empty()
|
||||
&& alerter_deletes.is_empty()
|
||||
&& repo_creates.is_empty()
|
||||
&& repo_updates.is_empty()
|
||||
&& repo_deletes.is_empty()
|
||||
&& procedure_creates.is_empty()
|
||||
&& procedure_updates.is_empty()
|
||||
&& procedure_deletes.is_empty()
|
||||
&& user_group_creates.is_empty()
|
||||
&& user_group_updates.is_empty()
|
||||
&& user_group_deletes.is_empty()
|
||||
@@ -88,6 +108,12 @@ pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
// No deps
|
||||
entities::sync::ResourceSync::run_updates(
|
||||
resource_sync_creates,
|
||||
resource_sync_updates,
|
||||
resource_sync_deletes,
|
||||
)
|
||||
.await;
|
||||
ServerTemplate::run_updates(
|
||||
server_template_creates,
|
||||
server_template_updates,
|
||||
@@ -116,7 +142,7 @@ pub async fn run(path: &str, delete: bool) -> anyhow::Result<()> {
|
||||
Build::run_updates(build_creates, build_updates, build_deletes)
|
||||
.await;
|
||||
|
||||
// Dependant on server / builder
|
||||
// Dependant on server / build
|
||||
Deployment::run_updates(
|
||||
deployment_creates,
|
||||
deployment_updates,
|
||||
|
||||
@@ -19,7 +19,7 @@ pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem<T> {
|
||||
pub struct ToUpdateItem<T: Default> {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<T>,
|
||||
pub update_description: bool,
|
||||
@@ -28,13 +28,17 @@ pub struct ToUpdateItem<T> {
|
||||
|
||||
pub trait ResourceSync: Sized {
|
||||
type Config: Clone
|
||||
+ Default
|
||||
+ Send
|
||||
+ From<Self::PartialConfig>
|
||||
+ PartialDiff<Self::PartialConfig, Self::ConfigDiff>
|
||||
+ 'static;
|
||||
type Info: Default + 'static;
|
||||
type PartialConfig: std::fmt::Debug
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Default
|
||||
+ From<Self::Config>
|
||||
+ From<Self::ConfigDiff>
|
||||
+ Serialize
|
||||
+ MaybeNone
|
||||
@@ -59,6 +63,10 @@ pub trait ResourceSync: Sized {
|
||||
resource: ResourceToml<Self::PartialConfig>,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// 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(
|
||||
@@ -66,6 +74,10 @@ pub trait ResourceSync: Sized {
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff>;
|
||||
|
||||
/// Apply any changes to computed config diff
|
||||
/// before logging
|
||||
fn validate_diff(_diff: &mut Self::ConfigDiff) {}
|
||||
|
||||
/// Deletes the target resource
|
||||
async fn delete(id_or_name: String) -> anyhow::Result<()>;
|
||||
|
||||
@@ -158,6 +170,7 @@ pub trait ResourceSync: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets all the resources to update, logging along the way.
|
||||
pub fn get_updates<Resource: ResourceSync>(
|
||||
resources: Vec<ResourceToml<Resource::PartialConfig>>,
|
||||
delete: bool,
|
||||
@@ -179,11 +192,20 @@ pub fn get_updates<Resource: ResourceSync>(
|
||||
for mut resource in resources {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
let diff = Resource::get_diff(
|
||||
// First 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: Resource::Config = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Resource::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Resource::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
)?;
|
||||
|
||||
Resource::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
|
||||
@@ -78,6 +78,14 @@ impl ResourceSync for Build {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
|
||||
fn validate_diff(diff: &mut Self::ConfigDiff) {
|
||||
if let Some((_, to)) = &diff.version {
|
||||
if to.is_none() {
|
||||
diff.version = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete(id: String) -> anyhow::Result<()> {
|
||||
monitor_client().write(DeleteBuild { id }).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -6,3 +6,4 @@ mod procedure;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod sync;
|
||||
|
||||
@@ -21,7 +21,7 @@ use partial_derive2::{MaybeNone, PartialDiff};
|
||||
use crate::{
|
||||
maps::{
|
||||
id_to_build, id_to_deployment, id_to_procedure, id_to_repo,
|
||||
id_to_server, name_to_procedure,
|
||||
id_to_resource_sync, id_to_server, name_to_procedure,
|
||||
},
|
||||
state::monitor_client,
|
||||
sync::resource::{
|
||||
@@ -133,6 +133,7 @@ impl ResourceSync for Procedure {
|
||||
Self::display()
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ impl ResourceSync for Procedure {
|
||||
to_create.retain(|resource| !to_pull.contains(&resource.name));
|
||||
|
||||
if to_update.is_empty() && to_create.is_empty() {
|
||||
info!("all procedures synced");
|
||||
// info!("all procedures synced");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +184,7 @@ impl ResourceSync for Procedure {
|
||||
for stage in &mut original.stages {
|
||||
for execution in &mut stage.executions {
|
||||
match &mut execution.execution {
|
||||
Execution::None(_) => {}
|
||||
Execution::None(_) | Execution::Sleep(_) => {}
|
||||
Execution::RunProcedure(config) => {
|
||||
config.procedure = id_to_procedure()
|
||||
.get(&config.procedure)
|
||||
@@ -256,6 +257,12 @@ impl ResourceSync for Procedure {
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RunSync(config) => {
|
||||
config.sync = id_to_resource_sync()
|
||||
.get(&config.sync)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateResourceSync, DeleteResourceSync, UpdateResourceSync,
|
||||
},
|
||||
entities::{
|
||||
self,
|
||||
resource::Resource,
|
||||
sync::{
|
||||
PartialResourceSyncConfig, ResourceSyncConfig,
|
||||
ResourceSyncConfigDiff, ResourceSyncInfo,
|
||||
},
|
||||
toml::ResourceToml,
|
||||
update::ResourceTarget,
|
||||
},
|
||||
};
|
||||
use partial_derive2::PartialDiff;
|
||||
|
||||
use crate::{
|
||||
maps::name_to_resource_sync, state::monitor_client,
|
||||
sync::resource::ResourceSync,
|
||||
};
|
||||
|
||||
impl ResourceSync for entities::sync::ResourceSync {
|
||||
type Config = ResourceSyncConfig;
|
||||
type Info = ResourceSyncInfo;
|
||||
type PartialConfig = PartialResourceSyncConfig;
|
||||
type ConfigDiff = ResourceSyncConfigDiff;
|
||||
|
||||
fn display() -> &'static str {
|
||||
"resource sync"
|
||||
}
|
||||
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
}
|
||||
|
||||
fn name_to_resource(
|
||||
) -> &'static HashMap<String, Resource<Self::Config, Self::Info>>
|
||||
{
|
||||
name_to_resource_sync()
|
||||
}
|
||||
|
||||
async fn create(
|
||||
resource: ResourceToml<Self::PartialConfig>,
|
||||
) -> anyhow::Result<String> {
|
||||
monitor_client()
|
||||
.write(CreateResourceSync {
|
||||
name: resource.name,
|
||||
config: resource.config,
|
||||
})
|
||||
.await
|
||||
.map(|res| res.id)
|
||||
}
|
||||
|
||||
async fn update(
|
||||
id: String,
|
||||
resource: ResourceToml<Self::PartialConfig>,
|
||||
) -> anyhow::Result<()> {
|
||||
monitor_client()
|
||||
.write(UpdateResourceSync {
|
||||
id,
|
||||
config: resource.config,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
|
||||
async fn delete(id: String) -> anyhow::Result<()> {
|
||||
monitor_client().write(DeleteResourceSync { id }).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ use monitor_client::{
|
||||
|
||||
use crate::maps::{
|
||||
id_to_alerter, id_to_build, id_to_builder, id_to_deployment,
|
||||
id_to_procedure, id_to_repo, id_to_server, id_to_server_template,
|
||||
id_to_user, name_to_user_group,
|
||||
id_to_procedure, id_to_repo, id_to_resource_sync, id_to_server,
|
||||
id_to_server_template, id_to_user, name_to_user_group,
|
||||
};
|
||||
|
||||
pub struct UpdateItem {
|
||||
@@ -146,6 +146,12 @@ pub async fn get_updates(
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = id_to_resource_sync()
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
PermissionToml {
|
||||
target: p.resource_target,
|
||||
@@ -234,6 +240,7 @@ pub async fn get_updates(
|
||||
"adding".dimmed()
|
||||
))
|
||||
}
|
||||
println!("{}", lines.join("\n-------------------\n"));
|
||||
to_update.push(UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
@@ -244,7 +251,7 @@ pub async fn get_updates(
|
||||
|
||||
for d in &to_delete {
|
||||
println!(
|
||||
"\n{}: variable: '{}'\n-------------------",
|
||||
"\n{}: user group: '{}'\n-------------------",
|
||||
"DELETE".red(),
|
||||
d.name.bold(),
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ path = "src/main.rs"
|
||||
monitor_client = { workspace = true, features = ["mongo"] }
|
||||
periphery_client.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
merge_config_files.workspace = true
|
||||
@@ -27,11 +28,13 @@ partial_derive2.workspace = true
|
||||
derive_variants.workspace = true
|
||||
mongo_indexed.workspace = true
|
||||
resolver_api.workspace = true
|
||||
toml_pretty.workspace = true
|
||||
parse_csl.workspace = true
|
||||
mungos.workspace = true
|
||||
slack.workspace = true
|
||||
svi.workspace = true
|
||||
# external
|
||||
ordered_hash_map.workspace = true
|
||||
urlencoding.workspace = true
|
||||
aws-sdk-ec2.workspace = true
|
||||
aws-config.workspace = true
|
||||
@@ -46,6 +49,7 @@ futures.workspace = true
|
||||
anyhow.workspace = true
|
||||
dotenv.workspace = true
|
||||
bcrypt.workspace = true
|
||||
base64.workspace = true
|
||||
tokio.workspace = true
|
||||
tower.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
+13
-1
@@ -14,9 +14,21 @@ RUN cd frontend && yarn link @monitor/client && yarn && yarn build
|
||||
|
||||
# Final Image
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt update && apt install -y ca-certificates
|
||||
|
||||
# Install Deps
|
||||
RUN apt update && apt install -y git ca-certificates
|
||||
|
||||
# Copy
|
||||
COPY ./config_example/core.config.example.toml /config/config.toml
|
||||
COPY --from=core-builder /builder/target/release/core /
|
||||
COPY --from=frontend-builder /builder/frontend/dist /frontend
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9000
|
||||
|
||||
# Label for Ghcr
|
||||
LABEL org.opencontainers.image.source=https://github.com/mbecker20/monitor
|
||||
LABEL org.opencontainers.image.description="A tool to build and deploy software across many servers"
|
||||
LABEL org.opencontainers.image.licenses=GPL-3.0
|
||||
|
||||
CMD ["./core"]
|
||||
@@ -8,7 +8,7 @@ use monitor_client::{
|
||||
},
|
||||
entities::{
|
||||
all_logs_success,
|
||||
build::Build,
|
||||
build::{Build, CloudRegistryConfig, ImageRegistry},
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
deployment::DeploymentState,
|
||||
monitor_timestamp,
|
||||
@@ -72,6 +72,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let registry_token =
|
||||
validate_account_extract_registry_token(&build)?;
|
||||
|
||||
// get the action state for the build (or insert default).
|
||||
let action_state =
|
||||
action_states().build.get_or_insert_default(&build.id).await;
|
||||
@@ -181,11 +184,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
if all_logs_success(&update.logs) {
|
||||
let docker_token = core_config
|
||||
.docker_accounts
|
||||
.get(&build.config.docker_account)
|
||||
.cloned();
|
||||
|
||||
// Interpolate variables / secrets into build args
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
@@ -237,7 +235,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
res = periphery
|
||||
.request(api::build::Build {
|
||||
build: build.clone(),
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
}) => res.context("failed at call to periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
@@ -282,6 +280,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
.await;
|
||||
}
|
||||
|
||||
// stop the cancel listening task from going forever
|
||||
cancel.cancel();
|
||||
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
@@ -675,3 +674,32 @@ fn start_aws_builder_log(
|
||||
|
||||
format!("instance id: {instance_id}\nip: {ip}\nami id: {ami_id}\ninstance type: {instance_type}\nvolume size: {volume_gb} GB\nsubnet id: {subnet_id}\nsecurity groups: {readable_sec_group_ids}\nassign public ip: {assign_public_ip}\nuse public ip: {use_public_ip}")
|
||||
}
|
||||
|
||||
/// This will make sure that a build with non-none image registry has an account attached,
|
||||
/// and will check the core config for a token matching requirements (otherwise it is left to periphery)
|
||||
fn validate_account_extract_registry_token(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match &build.config.image_registry {
|
||||
ImageRegistry::None(_) => Ok(None),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must attach account to use DockerHub image registry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().docker_accounts.get(account).cloned())
|
||||
}
|
||||
ImageRegistry::Ghcr(CloudRegistryConfig { account, .. }) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must attach account to use GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().github_accounts.get(account).cloned())
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
build::Build,
|
||||
build::{Build, ImageRegistry},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
get_image_name,
|
||||
permission::PermissionLevel,
|
||||
@@ -78,6 +78,8 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
|
||||
let periphery = periphery_client(&server)?;
|
||||
|
||||
// This block gets the version of the image to deploy in the Build case.
|
||||
// It also gets the name of the image from the build and attaches it directly.
|
||||
let version = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(&build_id).await?;
|
||||
@@ -91,10 +93,13 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{version}"),
|
||||
};
|
||||
// set docker account to match build docker account if it's not overridden by deployment
|
||||
if deployment.config.docker_account.is_empty() {
|
||||
deployment.config.docker_account =
|
||||
build.config.docker_account;
|
||||
// set image registry to match build docker account if it's not overridden by deployment
|
||||
if matches!(
|
||||
&deployment.config.image_registry,
|
||||
ImageRegistry::None(_)
|
||||
) {
|
||||
deployment.config.image_registry =
|
||||
build.config.image_registry;
|
||||
}
|
||||
version
|
||||
}
|
||||
@@ -156,17 +161,25 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
update.version = version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let docker_token = core_config
|
||||
.docker_accounts
|
||||
.get(&deployment.config.docker_account)
|
||||
.cloned();
|
||||
let registry_token = match &deployment.config.image_registry {
|
||||
ImageRegistry::None(_) => None,
|
||||
ImageRegistry::DockerHub(params) => {
|
||||
core_config.docker_accounts.get(¶ms.account).cloned()
|
||||
}
|
||||
ImageRegistry::Ghcr(params) => {
|
||||
core_config.github_accounts.get(¶ms.account).cloned()
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
return Err(anyhow!("Custom ImageRegistry not yet supported"))
|
||||
}
|
||||
};
|
||||
|
||||
match periphery
|
||||
.request(api::container::Deploy {
|
||||
deployment,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{middleware, routing::post, Extension, Router};
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{update::Update, user::User},
|
||||
entities::{
|
||||
update::{Log, Update},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::by_id::find_one_by_id;
|
||||
use resolver_api::{derive::Resolver, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serror::{serialize_error_pretty, Json};
|
||||
@@ -15,7 +19,7 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
auth::auth_request,
|
||||
helpers::update::{init_execution_update, update_update},
|
||||
state::State,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
mod build;
|
||||
@@ -24,6 +28,7 @@ mod procedure;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod sync;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
@@ -56,6 +61,9 @@ pub enum ExecuteRequest {
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
LaunchServer(LaunchServer),
|
||||
|
||||
// ==== SYNC ====
|
||||
RunSync(RunSync),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
@@ -79,23 +87,33 @@ async fn handler(
|
||||
tokio::spawn(task(req_id, request, user, update.clone()));
|
||||
|
||||
tokio::spawn({
|
||||
let mut update = update.clone();
|
||||
let update_id = update.id.clone();
|
||||
async move {
|
||||
match handle.await {
|
||||
let log = match handle.await {
|
||||
Ok(Err(e)) => {
|
||||
warn!("/execute request {req_id} task error: {e:#}",);
|
||||
update
|
||||
.push_error_log("task error", serialize_error_pretty(&e));
|
||||
update.finalize();
|
||||
let _ = update_update(update).await;
|
||||
Log::error("task error", serialize_error_pretty(&e))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("/execute request {req_id} spawn error: {e:?}",);
|
||||
update.push_error_log("spawn error", format!("{e:#?}"));
|
||||
update.finalize();
|
||||
let _ = update_update(update).await;
|
||||
Log::error("spawn error", format!("{e:#?}"))
|
||||
}
|
||||
_ => {}
|
||||
_ => return,
|
||||
};
|
||||
let res = async {
|
||||
let mut update =
|
||||
find_one_by_id(&db_client().await.updates, &update_id)
|
||||
.await
|
||||
.context("failed to query to db")?
|
||||
.context("no update exists with given id")?;
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
update_update(update).await
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Err(e) = res {
|
||||
warn!("failed to update update with task error log | {e:#}");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -103,7 +121,7 @@ async fn handler(
|
||||
Ok(Json(update))
|
||||
}
|
||||
|
||||
#[instrument(name = "ExecuteRequest", skip(user))]
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update))]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: ExecuteRequest,
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Resolve<RunProcedure, (User, Update)> for State {
|
||||
fn resolve_inner(
|
||||
procedure: String,
|
||||
user: User,
|
||||
update: Update,
|
||||
mut update: Update,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn std::future::Future<Output = anyhow::Result<Update>> + Send,
|
||||
@@ -46,6 +46,14 @@ fn resolve_inner(
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Need to push the initial log, as execute_procedure
|
||||
// assumes first log is already created
|
||||
// and will panic otherwise.
|
||||
update.push_simple_log(
|
||||
"execute_procedure",
|
||||
format!("executing procedure {}", procedure.name),
|
||||
);
|
||||
|
||||
// get the action state for the procedure (or insert default).
|
||||
let action_state = action_states()
|
||||
.procedure
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
entities::{
|
||||
self,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
update::{Log, Update},
|
||||
user::{sync_user, User},
|
||||
},
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_document};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::{
|
||||
colored,
|
||||
resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
},
|
||||
update::update_update,
|
||||
},
|
||||
resource::{self, refresh_resource_sync_state_cache},
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<RunSync, (User, Update)> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunSync { sync }: RunSync,
|
||||
(user, mut update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let sync = resource::get_check_permissions::<
|
||||
entities::sync::ResourceSync,
|
||||
>(&sync, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if sync.config.repo.is_empty() {
|
||||
return Err(anyhow!("resource sync repo not configured"));
|
||||
}
|
||||
|
||||
let (res, logs, hash, message) =
|
||||
crate::helpers::sync::remote::get_remote_resources(&sync)
|
||||
.await
|
||||
.context("failed to get remote resources")?;
|
||||
|
||||
update.logs.extend(logs);
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let resources = res?;
|
||||
|
||||
let all_resources = AllResourcesById::load().await?;
|
||||
let id_to_tags = get_id_to_tags(None).await?;
|
||||
|
||||
let (servers_to_create, servers_to_update, servers_to_delete) =
|
||||
get_updates_for_execution::<Server>(
|
||||
resources.servers,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
) = get_updates_for_execution::<Deployment>(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (builds_to_create, builds_to_update, builds_to_delete) =
|
||||
get_updates_for_execution::<Build>(
|
||||
resources.builds,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (repos_to_create, repos_to_update, repos_to_delete) =
|
||||
get_updates_for_execution::<Repo>(
|
||||
resources.repos,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
procedures_to_create,
|
||||
procedures_to_update,
|
||||
procedures_to_delete,
|
||||
) = get_updates_for_execution::<Procedure>(
|
||||
resources.procedures,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (builders_to_create, builders_to_update, builders_to_delete) =
|
||||
get_updates_for_execution::<Builder>(
|
||||
resources.builders,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (alerters_to_create, alerters_to_update, alerters_to_delete) =
|
||||
get_updates_for_execution::<Alerter>(
|
||||
resources.alerters,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
server_templates_to_create,
|
||||
server_templates_to_update,
|
||||
server_templates_to_delete,
|
||||
) = get_updates_for_execution::<ServerTemplate>(
|
||||
resources.server_templates,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
resource_syncs_to_create,
|
||||
resource_syncs_to_update,
|
||||
resource_syncs_to_delete,
|
||||
) = get_updates_for_execution::<entities::sync::ResourceSync>(
|
||||
resources.resource_syncs,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
variables_to_create,
|
||||
variables_to_update,
|
||||
variables_to_delete,
|
||||
) = crate::helpers::sync::variables::get_updates_for_execution(
|
||||
resources.variables,
|
||||
sync.config.delete,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
user_groups_to_create,
|
||||
user_groups_to_update,
|
||||
user_groups_to_delete,
|
||||
) = crate::helpers::sync::user_groups::get_updates_for_execution(
|
||||
resources.user_groups,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if resource_syncs_to_create.is_empty()
|
||||
&& resource_syncs_to_update.is_empty()
|
||||
&& resource_syncs_to_delete.is_empty()
|
||||
&& server_templates_to_create.is_empty()
|
||||
&& server_templates_to_update.is_empty()
|
||||
&& server_templates_to_delete.is_empty()
|
||||
&& servers_to_create.is_empty()
|
||||
&& servers_to_update.is_empty()
|
||||
&& servers_to_delete.is_empty()
|
||||
&& deployments_to_create.is_empty()
|
||||
&& deployments_to_update.is_empty()
|
||||
&& deployments_to_delete.is_empty()
|
||||
&& builds_to_create.is_empty()
|
||||
&& builds_to_update.is_empty()
|
||||
&& builds_to_delete.is_empty()
|
||||
&& builders_to_create.is_empty()
|
||||
&& builders_to_update.is_empty()
|
||||
&& builders_to_delete.is_empty()
|
||||
&& alerters_to_create.is_empty()
|
||||
&& alerters_to_update.is_empty()
|
||||
&& alerters_to_delete.is_empty()
|
||||
&& repos_to_create.is_empty()
|
||||
&& repos_to_update.is_empty()
|
||||
&& repos_to_delete.is_empty()
|
||||
&& procedures_to_create.is_empty()
|
||||
&& procedures_to_update.is_empty()
|
||||
&& procedures_to_delete.is_empty()
|
||||
&& user_groups_to_create.is_empty()
|
||||
&& user_groups_to_update.is_empty()
|
||||
&& user_groups_to_delete.is_empty()
|
||||
&& variables_to_create.is_empty()
|
||||
&& variables_to_update.is_empty()
|
||||
&& variables_to_delete.is_empty()
|
||||
{
|
||||
update.push_simple_log(
|
||||
"No Changes",
|
||||
format!("{}. exiting.", colored("nothing to do", "green")),
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
// =================
|
||||
|
||||
// No deps
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
crate::helpers::sync::variables::run_updates(
|
||||
variables_to_create,
|
||||
variables_to_update,
|
||||
variables_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
crate::helpers::sync::user_groups::run_updates(
|
||||
user_groups_to_create,
|
||||
user_groups_to_update,
|
||||
user_groups_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
entities::sync::ResourceSync::run_updates(
|
||||
resource_syncs_to_create,
|
||||
resource_syncs_to_update,
|
||||
resource_syncs_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
ServerTemplate::run_updates(
|
||||
server_templates_to_create,
|
||||
server_templates_to_update,
|
||||
server_templates_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Server::run_updates(
|
||||
servers_to_create,
|
||||
servers_to_update,
|
||||
servers_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Alerter::run_updates(
|
||||
alerters_to_create,
|
||||
alerters_to_update,
|
||||
alerters_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// Dependent on server
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Builder::run_updates(
|
||||
builders_to_create,
|
||||
builders_to_update,
|
||||
builders_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Repo::run_updates(
|
||||
repos_to_create,
|
||||
repos_to_update,
|
||||
repos_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// Dependant on builder
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Build::run_updates(
|
||||
builds_to_create,
|
||||
builds_to_update,
|
||||
builds_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// Dependant on server / build
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Deployment::run_updates(
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// Dependant on everything
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Procedure::run_updates(
|
||||
procedures_to_create,
|
||||
procedures_to_update,
|
||||
procedures_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
let db = db_client().await;
|
||||
|
||||
if let Err(e) = update_one_by_id(
|
||||
&db.resource_syncs,
|
||||
&sync.id,
|
||||
doc! {
|
||||
"$set": {
|
||||
"info.last_sync_ts": monitor_timestamp(),
|
||||
"info.last_sync_hash": hash,
|
||||
"info.last_sync_message": message,
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"failed to update resource sync {} info after sync | {e:#}",
|
||||
sync.name
|
||||
)
|
||||
}
|
||||
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
RefreshResourceSyncPending { sync: sync.id },
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to refresh sync {} after run | {e:#}", sync.name);
|
||||
update.push_error_log(
|
||||
"refresh sync",
|
||||
format!(
|
||||
"failed to refresh sync pending after run | {}",
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
// but will fail to update cache in that case.
|
||||
if let Ok(update_doc) = to_document(&update) {
|
||||
let _ = update_one_by_id(
|
||||
&db.updates,
|
||||
&update.id,
|
||||
mungos::update::Update::Set(update_doc),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
refresh_resource_sync_state_cache().await;
|
||||
}
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_extend(logs: &mut Vec<Log>, log: Option<Log>) {
|
||||
if let Some(log) = log {
|
||||
logs.push(log);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod auth;
|
||||
pub mod execute;
|
||||
pub mod read;
|
||||
pub mod user;
|
||||
pub mod write;
|
||||
pub mod user;
|
||||
@@ -13,7 +13,9 @@ use mungos::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config, helpers::query::get_resource_ids_for_non_admin, state::{db_client, State}
|
||||
config::core_config,
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
const NUM_ALERTS_PER_PAGE: u64 = 100;
|
||||
|
||||
@@ -246,6 +246,24 @@ impl Resolve<GetBuildVersions, User> for State {
|
||||
}
|
||||
}
|
||||
|
||||
fn github_organizations() -> &'static String {
|
||||
static GITHUB_ORGANIZATIONS: OnceLock<String> = OnceLock::new();
|
||||
GITHUB_ORGANIZATIONS.get_or_init(|| {
|
||||
serde_json::to_string(&core_config().github_organizations)
|
||||
.expect("failed to serialize github organizations")
|
||||
})
|
||||
}
|
||||
|
||||
impl ResolveToString<ListGithubOrganizations, User> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
ListGithubOrganizations {}: ListGithubOrganizations,
|
||||
_: User,
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(github_organizations().clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn docker_organizations() -> &'static String {
|
||||
static DOCKER_ORGANIZATIONS: OnceLock<String> = OnceLock::new();
|
||||
DOCKER_ORGANIZATIONS.get_or_init(|| {
|
||||
@@ -283,6 +301,8 @@ impl Resolve<ListCommonBuildExtraArgs, User> for State {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res.into_iter().collect())
|
||||
let mut res = res.into_iter().collect::<Vec<_>>();
|
||||
res.sort();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ impl Resolve<GetBuilderAvailableAccounts, User> for State {
|
||||
let res = self
|
||||
.resolve(
|
||||
read::GetAvailableAccounts {
|
||||
server: config.server_id,
|
||||
server: Some(config.server_id),
|
||||
},
|
||||
user,
|
||||
)
|
||||
|
||||
@@ -255,6 +255,8 @@ impl Resolve<ListCommonDeploymentExtraArgs, User> for State {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res.into_iter().collect())
|
||||
let mut res = res.into_iter().collect::<Vec<_>>();
|
||||
res.sort();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ mod repo;
|
||||
mod search;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod toml;
|
||||
mod update;
|
||||
@@ -105,6 +106,8 @@ enum ReadRequest {
|
||||
GetBuildVersions(GetBuildVersions),
|
||||
ListCommonBuildExtraArgs(ListCommonBuildExtraArgs),
|
||||
#[to_string_resolver]
|
||||
ListGithubOrganizations(ListGithubOrganizations),
|
||||
#[to_string_resolver]
|
||||
ListDockerOrganizations(ListDockerOrganizations),
|
||||
|
||||
// ==== REPO ====
|
||||
@@ -114,6 +117,13 @@ enum ReadRequest {
|
||||
ListFullRepos(ListFullRepos),
|
||||
GetRepoActionState(GetRepoActionState),
|
||||
|
||||
// ==== SYNC ====
|
||||
GetResourceSyncsSummary(GetResourceSyncsSummary),
|
||||
GetResourceSync(GetResourceSync),
|
||||
ListResourceSyncs(ListResourceSyncs),
|
||||
ListFullResourceSyncs(ListFullResourceSyncs),
|
||||
GetResourceSyncActionState(GetResourceSyncActionState),
|
||||
|
||||
// ==== BUILDER ====
|
||||
GetBuildersSummary(GetBuildersSummary),
|
||||
GetBuilder(GetBuilder),
|
||||
|
||||
@@ -389,18 +389,24 @@ impl Resolve<GetAvailableAccounts, User> for State {
|
||||
GetAvailableAccounts { server }: GetAvailableAccounts,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetAvailableAccountsResponse> {
|
||||
let server = resource::get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let (github, docker) = match server {
|
||||
Some(server) => {
|
||||
let server = resource::get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let GetAccountsResponse { github, docker } =
|
||||
periphery_client(&server)?
|
||||
.request(api::GetAccounts {})
|
||||
.await
|
||||
.context("failed to get accounts from periphery")?;
|
||||
let GetAccountsResponse { github, docker } =
|
||||
periphery_client(&server)?
|
||||
.request(api::GetAccounts {})
|
||||
.await
|
||||
.context("failed to get accounts from periphery")?;
|
||||
(github, docker)
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let mut github_set = HashSet::<String>::new();
|
||||
|
||||
@@ -435,10 +441,11 @@ impl Resolve<GetAvailableSecrets, User> for State {
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let secrets = periphery_client(&server)?
|
||||
let mut secrets = periphery_client(&server)?
|
||||
.request(api::GetSecrets {})
|
||||
.await
|
||||
.context("failed to get accounts from periphery")?;
|
||||
secrets.sort();
|
||||
Ok(secrets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
permission::PermissionLevel,
|
||||
sync::{
|
||||
PendingSyncUpdatesData, ResourceSync, ResourceSyncActionState,
|
||||
ResourceSyncListItem, ResourceSyncState,
|
||||
},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
resource,
|
||||
state::{action_states, resource_sync_state_cache, State},
|
||||
};
|
||||
|
||||
impl Resolve<GetResourceSync, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetResourceSync { sync }: GetResourceSync,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
&sync,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListResourceSyncs, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListResourceSyncs { query }: ListResourceSyncs,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<ResourceSyncListItem>> {
|
||||
resource::list_for_user::<ResourceSync>(query, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListFullResourceSyncs, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListFullResourceSyncs { query }: ListFullResourceSyncs,
|
||||
user: User,
|
||||
) -> anyhow::Result<ListFullResourceSyncsResponse> {
|
||||
resource::list_full_for_user::<ResourceSync>(query, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetResourceSyncActionState, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetResourceSyncActionState { sync }: GetResourceSyncActionState,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSyncActionState> {
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&sync,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let action_state = action_states()
|
||||
.resource_sync
|
||||
.get(&sync.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?;
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetResourceSyncsSummary, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetResourceSyncsSummary {}: GetResourceSyncsSummary,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetResourceSyncsSummaryResponse> {
|
||||
let resource_syncs =
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
&user,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resource_syncs from db")?;
|
||||
|
||||
let mut res = GetResourceSyncsSummaryResponse::default();
|
||||
|
||||
let cache = resource_sync_state_cache();
|
||||
let action_states = action_states();
|
||||
|
||||
for resource_sync in resource_syncs {
|
||||
res.total += 1;
|
||||
|
||||
match resource_sync.info.pending.data {
|
||||
PendingSyncUpdatesData::Ok(data) => {
|
||||
if !data.no_updates() {
|
||||
res.pending += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PendingSyncUpdatesData::Err(_) => {
|
||||
res.failed += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
match (
|
||||
cache.get(&resource_sync.id).await.unwrap_or_default(),
|
||||
action_states
|
||||
.resource_sync
|
||||
.get(&resource_sync.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?,
|
||||
) {
|
||||
(_, action_states) if action_states.syncing => {
|
||||
res.syncing += 1;
|
||||
}
|
||||
(ResourceSyncState::Ok, _) => res.ok += 1,
|
||||
(ResourceSyncState::Failed, _) => res.failed += 1,
|
||||
(ResourceSyncState::Unknown, _) => res.unknown += 1,
|
||||
// will never come off the cache in the building state, since that comes from action states
|
||||
(ResourceSyncState::Syncing, _) => {
|
||||
unreachable!()
|
||||
}
|
||||
(ResourceSyncState::Pending, _) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::read::{GetTag, ListTags},
|
||||
entities::{tag::Tag, user::User},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use mungos::{find::find_collect, mongodb::options::FindOptions};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
@@ -27,8 +28,12 @@ impl Resolve<ListTags, User> for State {
|
||||
ListTags { query }: ListTags,
|
||||
_: User,
|
||||
) -> anyhow::Result<Vec<Tag>> {
|
||||
find_collect(&db_client().await.tags, query, None)
|
||||
.await
|
||||
.context("failed to get tags from db")
|
||||
find_collect(
|
||||
&db_client().await.tags,
|
||||
query,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to get tags from db")
|
||||
}
|
||||
}
|
||||
|
||||
+328
-51
@@ -14,13 +14,18 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
deployment::{
|
||||
conversions_to_string, term_signal_labels_to_string,
|
||||
Deployment, DeploymentImage,
|
||||
},
|
||||
environment_vars_to_string,
|
||||
permission::{PermissionLevel, UserTarget},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
resource::Resource,
|
||||
resource::{Resource, ResourceQuery},
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
sync::ResourceSync,
|
||||
toml::{
|
||||
PermissionToml, ResourceToml, ResourcesToml, UserGroupToml,
|
||||
},
|
||||
@@ -29,43 +34,55 @@ use monitor_client::{
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use ordered_hash_map::OrderedHashMap;
|
||||
use partial_derive2::PartialDiff;
|
||||
use resolver_api::Resolve;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_user_user_group_ids,
|
||||
resource,
|
||||
resource::{self, MonitorResource},
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<ExportAllResourcesToToml, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ExportAllResourcesToToml {}: ExportAllResourcesToToml,
|
||||
ExportAllResourcesToToml { tags }: ExportAllResourcesToToml,
|
||||
user: User,
|
||||
) -> anyhow::Result<ExportAllResourcesToTomlResponse> {
|
||||
let mut targets = Vec::<ResourceTarget>::new();
|
||||
|
||||
targets.extend(
|
||||
resource::list_for_user::<Alerter>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Alerter(resource.id)),
|
||||
resource::list_for_user::<Alerter>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Alerter(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Builder>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Builder(resource.id)),
|
||||
resource::list_for_user::<Builder>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Builder(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Server>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Server(resource.id)),
|
||||
resource::list_for_user::<Server>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Server(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Deployment>(
|
||||
Default::default(),
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
@@ -73,26 +90,35 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
|
||||
.map(|resource| ResourceTarget::Deployment(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Build>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Build(resource.id)),
|
||||
resource::list_for_user::<Build>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Build(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Repo>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Repo(resource.id)),
|
||||
resource::list_for_user::<Repo>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Repo(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Procedure>(Default::default(), &user)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Procedure(resource.id)),
|
||||
resource::list_for_user::<Procedure>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Procedure(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ServerTemplate>(
|
||||
Default::default(),
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
@@ -147,7 +173,20 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
res.alerters.push(convert_resource(alerter, &names.tags))
|
||||
res
|
||||
.alerters
|
||||
.push(convert_resource::<Alerter>(alerter, &names.tags))
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
res
|
||||
.resource_syncs
|
||||
.push(convert_resource::<ResourceSync>(sync, &names.tags))
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
let template = resource::get_check_permissions::<
|
||||
@@ -156,9 +195,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
&id, &user, PermissionLevel::Read
|
||||
)
|
||||
.await?;
|
||||
res
|
||||
.server_templates
|
||||
.push(convert_resource(template, &names.tags))
|
||||
res.server_templates.push(
|
||||
convert_resource::<ServerTemplate>(template, &names.tags),
|
||||
)
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
let server = resource::get_check_permissions::<Server>(
|
||||
@@ -167,7 +206,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
res.servers.push(convert_resource(server, &names.tags))
|
||||
res
|
||||
.servers
|
||||
.push(convert_resource::<Server>(server, &names.tags))
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
let mut builder =
|
||||
@@ -183,7 +224,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
names.servers.get(&id).unwrap_or(&String::new()),
|
||||
)
|
||||
}
|
||||
res.builders.push(convert_resource(builder, &names.tags))
|
||||
res
|
||||
.builders
|
||||
.push(convert_resource::<Builder>(builder, &names.tags))
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
let mut build = resource::get_check_permissions::<Build>(
|
||||
@@ -199,7 +242,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
.get(&build.config.builder_id)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
res.builds.push(convert_resource(build, &names.tags))
|
||||
res
|
||||
.builds
|
||||
.push(convert_resource::<Build>(build, &names.tags))
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
let mut deployment = resource::get_check_permissions::<
|
||||
@@ -223,9 +268,10 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
names.builds.get(build_id).unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
res
|
||||
.deployments
|
||||
.push(convert_resource(deployment, &names.tags))
|
||||
res.deployments.push(convert_resource::<Deployment>(
|
||||
deployment,
|
||||
&names.tags,
|
||||
))
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
let mut repo = resource::get_check_permissions::<Repo>(
|
||||
@@ -241,7 +287,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
.get(&repo.config.server_id)
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
res.repos.push(convert_resource(repo, &names.tags))
|
||||
res.repos.push(convert_resource::<Repo>(repo, &names.tags))
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
add_procedure(&id, &mut res, &user, &names)
|
||||
@@ -265,7 +311,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
.context("failed to get variables from db")?;
|
||||
}
|
||||
|
||||
let toml = toml::to_string(&res)
|
||||
let toml = serialize_resources_toml(&res)
|
||||
.context("failed to serialize resources to toml")?;
|
||||
|
||||
Ok(ExportResourcesToTomlResponse { toml })
|
||||
@@ -343,14 +389,17 @@ async fn add_procedure(
|
||||
Execution::PruneContainers(exec) => exec.server.clone_from(
|
||||
names.servers.get(&exec.server).unwrap_or(&String::new()),
|
||||
),
|
||||
Execution::None(_) => continue,
|
||||
Execution::RunSync(exec) => exec.sync.clone_from(
|
||||
names.syncs.get(&exec.sync).unwrap_or(&String::new()),
|
||||
),
|
||||
Execution::Sleep(_) | Execution::None(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
.procedures
|
||||
.push(convert_resource(procedure, &names.tags));
|
||||
.push(convert_resource::<Procedure>(procedure, &names.tags));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -362,6 +411,7 @@ struct ResourceNames {
|
||||
repos: HashMap<String, String>,
|
||||
deployments: HashMap<String, String>,
|
||||
procedures: HashMap<String, String>,
|
||||
syncs: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ResourceNames {
|
||||
@@ -410,6 +460,12 @@ impl ResourceNames {
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
syncs: find_collect(&db.resource_syncs, None, None)
|
||||
.await
|
||||
.context("failed to get all resource syncs")?
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -462,13 +518,13 @@ async fn add_user_groups(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_resource<Config, Info: Default, PartialConfig>(
|
||||
resource: Resource<Config, Info>,
|
||||
fn convert_resource<R: MonitorResource>(
|
||||
resource: Resource<R::Config, R::Info>,
|
||||
tag_names: &HashMap<String, String>,
|
||||
) -> ResourceToml<PartialConfig>
|
||||
where
|
||||
Config: Into<PartialConfig>,
|
||||
{
|
||||
) -> ResourceToml<R::PartialConfig> {
|
||||
// This makes sure all non-necessary (defaulted) fields don't make it into final toml
|
||||
let partial: R::PartialConfig = resource.config.into();
|
||||
let config = R::Config::default().minimize_partial(partial);
|
||||
ResourceToml {
|
||||
name: resource.name,
|
||||
tags: resource
|
||||
@@ -477,6 +533,227 @@ where
|
||||
.filter_map(|t| tag_names.get(t).cloned())
|
||||
.collect(),
|
||||
description: resource.description,
|
||||
config: resource.config.into(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_resources_toml(
|
||||
resources: &ResourcesToml,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut res = String::new();
|
||||
|
||||
let options = toml_pretty::Options::default()
|
||||
.tab(" ")
|
||||
.skip_empty_string(true)
|
||||
.max_inline_array_length(30);
|
||||
|
||||
for server in &resources.servers {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[server]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&server, options)
|
||||
.context("failed to serialize servers to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for deployment in &resources.deployments {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[deployment]]\n");
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&deployment)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("deployment has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
if let Some(term_signal_labels) =
|
||||
&deployment.config.term_signal_labels
|
||||
{
|
||||
config.insert(
|
||||
"term_signal_labels".to_string(),
|
||||
Value::String(term_signal_labels_to_string(
|
||||
term_signal_labels,
|
||||
)),
|
||||
);
|
||||
}
|
||||
if let Some(ports) = &deployment.config.ports {
|
||||
config.insert(
|
||||
"ports".to_string(),
|
||||
Value::String(conversions_to_string(ports)),
|
||||
);
|
||||
}
|
||||
if let Some(volumes) = &deployment.config.volumes {
|
||||
config.insert(
|
||||
"volumes".to_string(),
|
||||
Value::String(conversions_to_string(volumes)),
|
||||
);
|
||||
}
|
||||
if let Some(environment) = &deployment.config.environment {
|
||||
config.insert(
|
||||
"environment".to_string(),
|
||||
Value::String(environment_vars_to_string(environment)),
|
||||
);
|
||||
}
|
||||
if let Some(labels) = &deployment.config.labels {
|
||||
config.insert(
|
||||
"labels".to_string(),
|
||||
Value::String(environment_vars_to_string(labels)),
|
||||
);
|
||||
}
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize deployments to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for build in &resources.builds {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&build)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("build has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
if let Some(version) = &build.config.version {
|
||||
config.insert(
|
||||
"version".to_string(),
|
||||
Value::String(version.to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(build_args) = &build.config.build_args {
|
||||
config.insert(
|
||||
"build_args".to_string(),
|
||||
Value::String(environment_vars_to_string(build_args)),
|
||||
);
|
||||
}
|
||||
if let Some(labels) = &build.config.labels {
|
||||
config.insert(
|
||||
"labels".to_string(),
|
||||
Value::String(environment_vars_to_string(labels)),
|
||||
);
|
||||
}
|
||||
res.push_str("[[build]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize builds to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for repo in &resources.repos {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[repo]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&repo, options)
|
||||
.context("failed to serialize repos to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for procedure in &resources.procedures {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
let mut parsed: OrderedHashMap<String, Value> =
|
||||
serde_json::from_str(&serde_json::to_string(&procedure)?)?;
|
||||
let config = parsed
|
||||
.get_mut("config")
|
||||
.context("procedure has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
|
||||
let stages = config
|
||||
.remove("stages")
|
||||
.context("procedure config has no stages")?;
|
||||
let stages = stages.as_array().context("stages is not array")?;
|
||||
|
||||
res.push_str("[[procedure]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&parsed, options)
|
||||
.context("failed to serialize procedures to toml")?,
|
||||
);
|
||||
|
||||
for stage in stages {
|
||||
res.push_str("\n\n[[procedure.config.stage]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(stage, options)
|
||||
.context("failed to serialize procedures to toml")?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for alerter in &resources.alerters {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[alerter]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&alerter, options)
|
||||
.context("failed to serialize alerters to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for builder in &resources.builders {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[builder]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&builder, options)
|
||||
.context("failed to serialize builders to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for server_template in &resources.server_templates {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[server_template]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&server_template, options)
|
||||
.context("failed to serialize server_templates to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for resource_sync in &resources.resource_syncs {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[resource_sync]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&resource_sync, options)
|
||||
.context("failed to serialize resource_syncs to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for variable in &resources.variables {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[variable]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&variable, options)
|
||||
.context("failed to serialize variables to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
for user_group in &resources.user_groups {
|
||||
if !res.is_empty() {
|
||||
res.push_str("\n\n##\n\n");
|
||||
}
|
||||
res.push_str("[[user_group]]\n");
|
||||
res.push_str(
|
||||
&toml_pretty::to_string(&user_group, options)
|
||||
.context("failed to serialize user_groups to toml")?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use monitor_client::{
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
sync::ResourceSync,
|
||||
update::{
|
||||
ResourceTarget, ResourceTargetVariant, Update, UpdateListItem,
|
||||
},
|
||||
@@ -238,6 +239,14 @@ impl Resolve<GetUpdate, User> for State {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use monitor_client::{
|
||||
entities::user::{User, UserConfig},
|
||||
};
|
||||
use mungos::{
|
||||
by_id::find_one_by_id, find::find_collect, mongodb::bson::doc,
|
||||
by_id::find_one_by_id,
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOptions},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
@@ -47,10 +49,13 @@ impl Resolve<ListUsers, User> for State {
|
||||
if !user.admin {
|
||||
return Err(anyhow!("this route is only accessable by admins"));
|
||||
}
|
||||
let mut users =
|
||||
find_collect(&db_client().await.users, None, None)
|
||||
.await
|
||||
.context("failed to pull users from db")?;
|
||||
let mut users = find_collect(
|
||||
&db_client().await.users,
|
||||
None,
|
||||
FindOptions::builder().sort(doc! { "username": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to pull users from db")?;
|
||||
users.iter_mut().for_each(|user| user.sanitize());
|
||||
Ok(users)
|
||||
}
|
||||
@@ -65,7 +70,7 @@ impl Resolve<ListApiKeys, User> for State {
|
||||
let api_keys = find_collect(
|
||||
&db_client().await.api_keys,
|
||||
doc! { "user_id": &user.id },
|
||||
None,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for api keys")?
|
||||
|
||||
@@ -10,7 +10,10 @@ use monitor_client::{
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::bson::{doc, oid::ObjectId, Document},
|
||||
mongodb::{
|
||||
bson::{doc, oid::ObjectId, Document},
|
||||
options::FindOptions,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
@@ -51,8 +54,12 @@ impl Resolve<ListUserGroups, User> for State {
|
||||
if !user.admin {
|
||||
filter.insert("users", &user.id);
|
||||
}
|
||||
find_collect(&db_client().await.user_groups, filter, None)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")
|
||||
find_collect(
|
||||
&db_client().await.user_groups,
|
||||
filter,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::read::{
|
||||
GetVariable, GetVariableResponse, ListVariables,
|
||||
@@ -6,7 +7,7 @@ use monitor_client::{
|
||||
},
|
||||
entities::user::User,
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use mungos::{find::find_collect, mongodb::options::FindOptions};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
@@ -31,10 +32,13 @@ impl Resolve<ListVariables, User> for State {
|
||||
ListVariables {}: ListVariables,
|
||||
_: User,
|
||||
) -> anyhow::Result<ListVariablesResponse> {
|
||||
let variables =
|
||||
find_collect(&db_client().await.variables, None, None)
|
||||
.await
|
||||
.context("failed to query db for variables")?;
|
||||
let variables = find_collect(
|
||||
&db_client().await.variables,
|
||||
None,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for variables")?;
|
||||
Ok(ListVariablesResponse {
|
||||
variables,
|
||||
secrets: core_config().secrets.keys().cloned().collect(),
|
||||
|
||||
@@ -78,7 +78,11 @@ async fn handler(
|
||||
const RECENTLY_VIEWED_MAX: usize = 10;
|
||||
|
||||
impl Resolve<PushRecentlyViewed, User> for State {
|
||||
#[instrument(name = "PushRecentlyViewed", level = "debug", skip(self, user))]
|
||||
#[instrument(
|
||||
name = "PushRecentlyViewed",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PushRecentlyViewed { resource }: PushRecentlyViewed,
|
||||
@@ -127,7 +131,11 @@ impl Resolve<PushRecentlyViewed, User> for State {
|
||||
}
|
||||
|
||||
impl Resolve<SetLastSeenUpdate, User> for State {
|
||||
#[instrument(name = "SetLastSeenUpdate", level = "debug", skip(self, user))]
|
||||
#[instrument(
|
||||
name = "SetLastSeenUpdate",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
SetLastSeenUpdate {}: SetLastSeenUpdate,
|
||||
|
||||
@@ -28,13 +28,10 @@ impl Resolve<CopyAlerter, User> for State {
|
||||
CopyAlerter { name, id }: CopyAlerter,
|
||||
user: User,
|
||||
) -> anyhow::Result<Alerter> {
|
||||
let Alerter {
|
||||
config,
|
||||
..
|
||||
} = resource::get_check_permissions::<Alerter>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
let Alerter { config, .. } = resource::get_check_permissions::<
|
||||
Alerter,
|
||||
>(
|
||||
&id, &user, PermissionLevel::Write
|
||||
)
|
||||
.await?;
|
||||
resource::create::<Alerter>(&name, config.into(), &user).await
|
||||
|
||||
@@ -24,15 +24,13 @@ impl Resolve<CopyBuild, User> for State {
|
||||
CopyBuild { name, id }: CopyBuild,
|
||||
user: User,
|
||||
) -> anyhow::Result<Build> {
|
||||
let Build {
|
||||
config,
|
||||
..
|
||||
} = resource::get_check_permissions::<Build>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
let Build { config, .. } =
|
||||
resource::get_check_permissions::<Build>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
resource::create::<Build>(&name, config.into(), &user).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use monitor_client::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate,
|
||||
update::ResourceTarget, user::User,
|
||||
sync::ResourceSync, update::ResourceTarget, user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
@@ -92,6 +92,14 @@ impl Resolve<UpdateDescription, User> for State {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
resource::update_description::<ResourceSync>(
|
||||
&id,
|
||||
&description,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(UpdateDescriptionResponse {})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod service_user;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
@@ -32,7 +33,7 @@ mod variable;
|
||||
#[resolver_target(State)]
|
||||
#[resolver_args(User)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
enum WriteRequest {
|
||||
pub enum WriteRequest {
|
||||
// ==== SERVICE USER ====
|
||||
CreateServiceUser(CreateServiceUser),
|
||||
UpdateServiceUserDescription(UpdateServiceUserDescription),
|
||||
@@ -105,6 +106,13 @@ enum WriteRequest {
|
||||
DeleteProcedure(DeleteProcedure),
|
||||
UpdateProcedure(UpdateProcedure),
|
||||
|
||||
// ==== SYNC ====
|
||||
CreateResourceSync(CreateResourceSync),
|
||||
CopyResourceSync(CopyResourceSync),
|
||||
DeleteResourceSync(DeleteResourceSync),
|
||||
UpdateResourceSync(UpdateResourceSync),
|
||||
RefreshResourceSyncPending(RefreshResourceSyncPending),
|
||||
|
||||
// ==== TAG ====
|
||||
CreateTag(CreateTag),
|
||||
DeleteTag(DeleteTag),
|
||||
|
||||
@@ -304,5 +304,20 @@ async fn extract_resource_target_with_validation(
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::ServerTemplate, id))
|
||||
}
|
||||
ResourceTarget::ResourceSync(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.await
|
||||
.resource_syncs
|
||||
.find_one(filter, None)
|
||||
.await
|
||||
.context("failed to query db for resource syncs")?
|
||||
.context("no matching resource sync found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::ResourceSync, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
self,
|
||||
alert::{Alert, AlertData},
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::{stats::SeverityLevel, Server},
|
||||
server_template::ServerTemplate,
|
||||
sync::{
|
||||
PendingSyncUpdates, PendingSyncUpdatesData,
|
||||
PendingSyncUpdatesDataErr, PendingSyncUpdatesDataOk,
|
||||
ResourceSync,
|
||||
},
|
||||
update::ResourceTarget,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::{
|
||||
by_id::update_one_by_id,
|
||||
mongodb::bson::{doc, to_document},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::resource::{get_updates_for_view, AllResourcesById},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<CreateResourceSync, User> for State {
|
||||
#[instrument(name = "CreateResourceSync", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateResourceSync { name, config }: CreateResourceSync,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
resource::create::<ResourceSync>(&name, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CopyResourceSync, User> for State {
|
||||
#[instrument(name = "CopyResourceSync", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CopyResourceSync { name, id }: CopyResourceSync,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
let ResourceSync { config, .. } =
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
resource::create::<ResourceSync>(&name, config.into(), &user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteResourceSync, User> for State {
|
||||
#[instrument(name = "DeleteResourceSync", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteResourceSync { id }: DeleteResourceSync,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
resource::delete::<ResourceSync>(&id, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<UpdateResourceSync, User> for State {
|
||||
#[instrument(name = "UpdateResourceSync", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateResourceSync { id, config }: UpdateResourceSync,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
resource::update::<ResourceSync>(&id, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RefreshResourceSyncPending { sync }: RefreshResourceSyncPending,
|
||||
user: User,
|
||||
) -> anyhow::Result<ResourceSync> {
|
||||
// Even though this is a write request, this doesn't change any config. Anyone that can execute the
|
||||
// sync should be able to do this.
|
||||
let sync = resource::get_check_permissions::<
|
||||
entities::sync::ResourceSync,
|
||||
>(&sync, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if sync.config.repo.is_empty() {
|
||||
return Err(anyhow!("resource sync repo not configured"));
|
||||
}
|
||||
|
||||
let res = async {
|
||||
let (res, _, hash, message) =
|
||||
crate::helpers::sync::remote::get_remote_resources(&sync)
|
||||
.await
|
||||
.context("failed to get remote resources")?;
|
||||
let resources = res?;
|
||||
|
||||
let all_resources = AllResourcesById::load().await?;
|
||||
let id_to_tags = get_id_to_tags(None).await?;
|
||||
|
||||
let data = PendingSyncUpdatesDataOk {
|
||||
server_updates: get_updates_for_view::<Server>(
|
||||
resources.servers,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get server updates")?,
|
||||
deployment_updates: get_updates_for_view::<Deployment>(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get deployment updates")?,
|
||||
build_updates: get_updates_for_view::<Build>(
|
||||
resources.builds,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get build updates")?,
|
||||
repo_updates: get_updates_for_view::<Repo>(
|
||||
resources.repos,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get repo updates")?,
|
||||
procedure_updates: get_updates_for_view::<Procedure>(
|
||||
resources.procedures,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get procedure updates")?,
|
||||
alerter_updates: get_updates_for_view::<Alerter>(
|
||||
resources.alerters,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get alerter updates")?,
|
||||
builder_updates: get_updates_for_view::<Builder>(
|
||||
resources.builders,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get builder updates")?,
|
||||
server_template_updates:
|
||||
get_updates_for_view::<ServerTemplate>(
|
||||
resources.server_templates,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get server template updates")?,
|
||||
resource_sync_updates: get_updates_for_view::<
|
||||
entities::sync::ResourceSync,
|
||||
>(
|
||||
resources.resource_syncs,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
&id_to_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resource sync updates")?,
|
||||
variable_updates:
|
||||
crate::helpers::sync::variables::get_updates_for_view(
|
||||
resources.variables,
|
||||
sync.config.delete,
|
||||
)
|
||||
.await
|
||||
.context("failed to get variable updates")?,
|
||||
user_group_updates:
|
||||
crate::helpers::sync::user_groups::get_updates_for_view(
|
||||
resources.user_groups,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
)
|
||||
.await
|
||||
.context("failed to get user group updates")?,
|
||||
};
|
||||
anyhow::Ok((hash, message, data))
|
||||
}
|
||||
.await;
|
||||
|
||||
let (pending, has_updates) = match res {
|
||||
Ok((hash, message, data)) => {
|
||||
let has_updates = !data.no_updates();
|
||||
(
|
||||
PendingSyncUpdates {
|
||||
hash: Some(hash),
|
||||
message: Some(message),
|
||||
data: PendingSyncUpdatesData::Ok(data),
|
||||
},
|
||||
has_updates,
|
||||
)
|
||||
}
|
||||
Err(e) => (
|
||||
PendingSyncUpdates {
|
||||
hash: None,
|
||||
message: None,
|
||||
data: PendingSyncUpdatesData::Err(
|
||||
PendingSyncUpdatesDataErr {
|
||||
message: serialize_error_pretty(&e),
|
||||
},
|
||||
),
|
||||
},
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
let pending = to_document(&pending)
|
||||
.context("failed to serialize pending to document")?;
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().await.resource_syncs,
|
||||
&sync.id,
|
||||
doc! { "$set": { "info.pending": pending } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// check to update alert
|
||||
let id = sync.id.clone();
|
||||
let name = sync.name.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let db = db_client().await;
|
||||
let Some(existing) = db_client()
|
||||
.await
|
||||
.alerts
|
||||
.find_one(
|
||||
doc! {
|
||||
"resolved": false,
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": &id,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for alert")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
match (existing, has_updates) {
|
||||
// OPEN A NEW ALERT
|
||||
(None, true) => {
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts: monitor_timestamp(),
|
||||
resolved: false,
|
||||
level: SeverityLevel::Ok,
|
||||
target: ResourceTarget::ResourceSync(id.clone()),
|
||||
data: AlertData::ResourceSyncPendingUpdates { id, name },
|
||||
resolved_ts: None,
|
||||
};
|
||||
db.alerts
|
||||
.insert_one(&alert, None)
|
||||
.await
|
||||
.context("failed to open existing pending resource sync updates alert")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
.ok();
|
||||
}
|
||||
// CLOSE ALERT
|
||||
(Some(existing), false) => {
|
||||
update_one_by_id(
|
||||
&db.alerts,
|
||||
&existing.id,
|
||||
doc! {
|
||||
"$set": {
|
||||
"resolved": true,
|
||||
"resolved_ts": monitor_timestamp()
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to close existing pending resource sync updates alert")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
.ok();
|
||||
}
|
||||
// NOTHING TO DO
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
crate::resource::get::<ResourceSync>(&sync.id).await
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use monitor_client::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
procedure::Procedure, repo::Repo, server::Server,
|
||||
server_template::ServerTemplate, tag::Tag,
|
||||
server_template::ServerTemplate, sync::ResourceSync, tag::Tag,
|
||||
update::ResourceTarget, user::User,
|
||||
},
|
||||
};
|
||||
@@ -191,6 +191,15 @@ impl Resolve<UpdateTagsOnResource, User> for State {
|
||||
resource::update_tags::<ServerTemplate>(&id, tags, user)
|
||||
.await?
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
resource::update_tags::<ResourceSync>(&id, tags, user).await?
|
||||
}
|
||||
};
|
||||
Ok(UpdateTagsOnResourceResponse {})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use jwt::Token;
|
||||
use monitor_client::entities::config::core::{CoreConfig, OauthCredentials};
|
||||
use monitor_client::entities::config::core::{
|
||||
CoreConfig, OauthCredentials,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -12,6 +12,7 @@ use aws_sdk_ec2::{
|
||||
},
|
||||
Client,
|
||||
};
|
||||
use base64::Engine;
|
||||
use monitor_client::entities::{
|
||||
alert::{Alert, AlertData},
|
||||
monitor_timestamp,
|
||||
@@ -92,7 +93,10 @@ pub async fn launch_ec2_instance(
|
||||
)
|
||||
.min_count(1)
|
||||
.max_count(1)
|
||||
.user_data(user_data);
|
||||
.user_data(
|
||||
base64::engine::general_purpose::STANDARD_NO_PAD
|
||||
.encode(user_data),
|
||||
);
|
||||
|
||||
for volume in volumes {
|
||||
let ebs = EbsBlockDevice::builder()
|
||||
|
||||
@@ -54,6 +54,12 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
jwt_valid_for: env
|
||||
.monitor_jwt_valid_for
|
||||
.unwrap_or(config.jwt_valid_for),
|
||||
sync_directory: env
|
||||
.monitor_sync_directory
|
||||
.map(|dir|
|
||||
dir.parse()
|
||||
.context("failed to parse env MONITOR_SYNC_DIRECTORY as valid path").unwrap())
|
||||
.unwrap_or(config.sync_directory),
|
||||
monitoring_interval: env
|
||||
.monitor_monitoring_interval
|
||||
.unwrap_or(config.monitoring_interval),
|
||||
@@ -69,6 +75,8 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
github_webhook_base_url: env
|
||||
.monitor_github_webhook_base_url
|
||||
.or(config.github_webhook_base_url),
|
||||
github_organizations: env.monitor_github_organizations
|
||||
.unwrap_or(config.github_organizations),
|
||||
docker_organizations: env
|
||||
.monitor_docker_organizations
|
||||
.unwrap_or(config.docker_organizations),
|
||||
|
||||
@@ -12,6 +12,7 @@ use monitor_client::entities::{
|
||||
repo::Repo,
|
||||
server::{stats::SystemStatsRecord, Server},
|
||||
server_template::ServerTemplate,
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::Update,
|
||||
user::User,
|
||||
@@ -42,6 +43,7 @@ pub struct DbClient {
|
||||
pub procedures: Collection<Procedure>,
|
||||
pub alerters: Collection<Alerter>,
|
||||
pub server_templates: Collection<ServerTemplate>,
|
||||
pub resource_syncs: Collection<ResourceSync>,
|
||||
//
|
||||
pub db: Database,
|
||||
}
|
||||
@@ -101,6 +103,8 @@ impl DbClient {
|
||||
procedures: resource_collection(&db, "Procedure").await?,
|
||||
server_templates: resource_collection(&db, "ServerTemplate")
|
||||
.await?,
|
||||
resource_syncs: resource_collection(&db, "ResourceSync")
|
||||
.await?,
|
||||
//
|
||||
db,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ use monitor_client::{
|
||||
entities::{
|
||||
build::BuildActionState, deployment::DeploymentActionState,
|
||||
procedure::ProcedureActionState, repo::RepoActionState,
|
||||
server::ServerActionState,
|
||||
server::ServerActionState, sync::ResourceSyncActionState,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ pub struct ActionStates {
|
||||
pub repo: Cache<String, Arc<ActionState<RepoActionState>>>,
|
||||
pub procedure:
|
||||
Cache<String, Arc<ActionState<ProcedureActionState>>>,
|
||||
pub resource_sync:
|
||||
Cache<String, Arc<ActionState<ResourceSyncActionState>>>,
|
||||
}
|
||||
|
||||
/// Need to be able to check "busy" with write lock acquired.
|
||||
|
||||
@@ -246,6 +246,17 @@ async fn send_slack_alert(
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::ResourceSyncPendingUpdates { id, name } => {
|
||||
let text =
|
||||
format!("{level} | There are pending resource sync updates");
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"sync id: **{id}**\nsync name: **{name}**"
|
||||
)),
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::None {} => Default::default(),
|
||||
};
|
||||
if !text.is_empty() {
|
||||
|
||||
@@ -20,6 +20,7 @@ pub mod channel;
|
||||
pub mod procedure;
|
||||
pub mod prune;
|
||||
pub mod query;
|
||||
pub mod sync;
|
||||
pub mod update;
|
||||
|
||||
// pub mod resource;
|
||||
|
||||
@@ -5,8 +5,7 @@ use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
monitor_timestamp, procedure::Procedure, update::Update,
|
||||
user::procedure_user,
|
||||
procedure::Procedure, update::Update, user::procedure_user,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
@@ -16,19 +15,18 @@ use crate::{api::execute::ExecuteRequest, state::State};
|
||||
|
||||
use super::update::{init_execution_update, update_update};
|
||||
|
||||
#[instrument]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn execute_procedure(
|
||||
procedure: &Procedure,
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
for stage in &procedure.config.stages {
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!("executing stage: {}", stage.name),
|
||||
)
|
||||
.await;
|
||||
let timer = Instant::now();
|
||||
execute_stage(
|
||||
stage
|
||||
.executions
|
||||
@@ -42,21 +40,18 @@ pub async fn execute_procedure(
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
let time = Duration::from_millis(
|
||||
(monitor_timestamp() - start_ts) as u64,
|
||||
);
|
||||
format!(
|
||||
"failed stage '{}' execution after {time:?}",
|
||||
stage.name
|
||||
"failed stage '{}' execution after {:?}",
|
||||
stage.name,
|
||||
timer.elapsed(),
|
||||
)
|
||||
})?;
|
||||
let time =
|
||||
Duration::from_millis((monitor_timestamp() - start_ts) as u64);
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished stage '{}' execution in {time:?} ✅",
|
||||
stage.name
|
||||
"finished stage '{}' execution in {:?} ✅",
|
||||
stage.name,
|
||||
timer.elapsed()
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@@ -65,7 +60,39 @@ pub async fn execute_procedure(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[instrument(skip(update))]
|
||||
async fn execute_stage(
|
||||
executions: Vec<Execution>,
|
||||
parent_id: &str,
|
||||
parent_name: &str,
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
let futures = executions.into_iter().map(|execution| async move {
|
||||
let now = Instant::now();
|
||||
add_line_to_update(update, &format!("executing: {execution:?}"))
|
||||
.await;
|
||||
let fail_log = format!("failed on {execution:?}");
|
||||
let res =
|
||||
execute_execution(execution.clone(), parent_id, parent_name)
|
||||
.await
|
||||
.context(fail_log);
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished execution in {:?}: {execution:?}",
|
||||
now.elapsed()
|
||||
),
|
||||
)
|
||||
.await;
|
||||
res
|
||||
});
|
||||
join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_execution(
|
||||
execution: Execution,
|
||||
// used to prevent recursive procedure
|
||||
@@ -210,6 +237,27 @@ async fn execute_execution(
|
||||
.await
|
||||
.context("failed at PruneContainers")?
|
||||
}
|
||||
Execution::RunSync(req) => {
|
||||
let req = ExecuteRequest::RunSync(req);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunSync(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunSync")?
|
||||
}
|
||||
Execution::Sleep(req) => {
|
||||
tokio::time::sleep(Duration::from_millis(
|
||||
req.duration_ms as u64,
|
||||
))
|
||||
.await;
|
||||
Update {
|
||||
success: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
if update.success {
|
||||
Ok(())
|
||||
@@ -221,39 +269,6 @@ async fn execute_execution(
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(update))]
|
||||
async fn execute_stage(
|
||||
executions: Vec<Execution>,
|
||||
parent_id: &str,
|
||||
parent_name: &str,
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
let futures = executions.into_iter().map(|execution| async move {
|
||||
let now = Instant::now();
|
||||
add_line_to_update(update, &format!("executing: {execution:?}"))
|
||||
.await;
|
||||
let fail_log = format!("failed on {execution:?}");
|
||||
let res =
|
||||
execute_execution(execution.clone(), parent_id, parent_name)
|
||||
.await
|
||||
.context(fail_log);
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished execution in {:?}: {execution:?}",
|
||||
now.elapsed()
|
||||
),
|
||||
)
|
||||
.await;
|
||||
res
|
||||
});
|
||||
join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ASSUMES FIRST LOG IS ALREADY CREATED
|
||||
#[instrument(level = "debug")]
|
||||
async fn add_line_to_update(update: &Mutex<Update>, line: &str) {
|
||||
|
||||
@@ -9,14 +9,18 @@ use monitor_client::entities::{
|
||||
permission::PermissionLevel,
|
||||
server::{Server, ServerState},
|
||||
tag::Tag,
|
||||
update::ResourceTargetVariant,
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::{admin_service_user, User},
|
||||
variable::Variable,
|
||||
Operation,
|
||||
};
|
||||
use mungos::{
|
||||
by_id::find_one_by_id,
|
||||
find::find_collect,
|
||||
mongodb::bson::{doc, oid::ObjectId, Document},
|
||||
mongodb::{
|
||||
bson::{doc, oid::ObjectId, Document},
|
||||
options::FindOneOptions,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{config::core_config, resource, state::db_client};
|
||||
@@ -103,6 +107,18 @@ pub async fn get_tag_check_owner(
|
||||
Err(anyhow!("user must be tag owner or admin"))
|
||||
}
|
||||
|
||||
pub async fn get_id_to_tags(
|
||||
filter: impl Into<Option<Document>>,
|
||||
) -> anyhow::Result<HashMap<String, Tag>> {
|
||||
let res = find_collect(&db_client().await.tags, filter, None)
|
||||
.await
|
||||
.context("failed to query db for tags")?
|
||||
.into_iter()
|
||||
.map(|tag| (tag.id.clone(), tag))
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user_user_group_ids(
|
||||
user_id: &str,
|
||||
@@ -231,3 +247,25 @@ pub async fn get_variable(name: &str) -> anyhow::Result<Variable> {
|
||||
format!("no variable found with given name: {name}")
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_latest_update(
|
||||
resource_type: ResourceTargetVariant,
|
||||
id: &str,
|
||||
operation: Operation,
|
||||
) -> anyhow::Result<Option<Update>> {
|
||||
db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": resource_type.as_ref(),
|
||||
"target.id": id,
|
||||
"operation": operation.as_ref()
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for latest update")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{toml::ResourcesToml, update::Log};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::{colored, muted};
|
||||
|
||||
pub fn read_resources(
|
||||
path: &Path,
|
||||
) -> anyhow::Result<(ResourcesToml, Log)> {
|
||||
let mut res = ResourcesToml::default();
|
||||
let mut log = format!("reading resources from {path:?}");
|
||||
read_resources_recursive(path, &mut res, &mut log)?;
|
||||
Ok((res, Log::simple("read remote resources", log)))
|
||||
}
|
||||
|
||||
fn read_resources_recursive(
|
||||
path: &Path,
|
||||
resources: &mut ResourcesToml,
|
||||
log: &mut String,
|
||||
) -> anyhow::Result<()> {
|
||||
let res =
|
||||
fs::metadata(path).context("failed to get path metadata")?;
|
||||
if res.is_file() {
|
||||
if !path
|
||||
.extension()
|
||||
.map(|ext| ext == "toml")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let more = match parse_toml_file::<ResourcesToml>(path) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("failed to parse {:?}. skipping file | {e:#}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: {} from {}",
|
||||
muted("INFO"),
|
||||
colored("adding resources", "green"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
));
|
||||
|
||||
resources.servers.extend(more.servers);
|
||||
resources.deployments.extend(more.deployments);
|
||||
resources.builds.extend(more.builds);
|
||||
resources.repos.extend(more.repos);
|
||||
resources.procedures.extend(more.procedures);
|
||||
resources.builders.extend(more.builders);
|
||||
resources.alerters.extend(more.alerters);
|
||||
resources.server_templates.extend(more.server_templates);
|
||||
resources.resource_syncs.extend(more.resource_syncs);
|
||||
resources.user_groups.extend(more.user_groups);
|
||||
resources.variables.extend(more.variables);
|
||||
Ok(())
|
||||
} else if res.is_dir() {
|
||||
let directory = fs::read_dir(path)
|
||||
.context("failed to read directory contents")?;
|
||||
for entry in directory.into_iter().flatten() {
|
||||
let path = entry.path();
|
||||
if let Err(e) = read_resources_recursive(&path, resources, log)
|
||||
{
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: failed to read additional resources from {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("resources path is neither file nor directory"))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_toml_file<T: DeserializeOwned>(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> anyhow::Result<T> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.context("failed to read file contents")?;
|
||||
toml::from_str(&contents).context("failed to parse toml contents")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use async_timing_util::{wait_until_timelength, Timelength};
|
||||
use monitor_client::{
|
||||
api::write::RefreshResourceSyncPending, entities::user::sync_user,
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
pub mod remote;
|
||||
pub mod resource;
|
||||
pub mod user_groups;
|
||||
pub mod variables;
|
||||
|
||||
mod file;
|
||||
mod resources;
|
||||
|
||||
pub fn spawn_sync_refresh_loop() {
|
||||
tokio::spawn(async move {
|
||||
let db = db_client().await;
|
||||
let user = sync_user();
|
||||
loop {
|
||||
wait_until_timelength(Timelength::FiveMinutes, 0).await;
|
||||
let Ok(syncs) = find_collect(&db.resource_syncs, None, None)
|
||||
.await
|
||||
.inspect_err(|e| warn!("failed to get resource syncs from db in refresh task | {e:#}")) else {
|
||||
continue;
|
||||
};
|
||||
for sync in syncs {
|
||||
State
|
||||
.resolve(
|
||||
RefreshResourceSyncPending { sync: sync.id },
|
||||
user.clone(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to refresh resource sync in refresh task | sync: {} | {e:#}", sync.name)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn muted(content: &str) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(content: &str, color: &str) -> String {
|
||||
format!("<span class=\"text-{color}-500\">{content}</span>")
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
sync::ResourceSync, to_monitor_name, toml::ResourcesToml,
|
||||
update::Log, CloneArgs, LatestCommit,
|
||||
};
|
||||
|
||||
use crate::{config::core_config, state::resource_sync_lock_cache};
|
||||
|
||||
pub async fn get_remote_resources(
|
||||
sync: &ResourceSync,
|
||||
) -> anyhow::Result<(
|
||||
anyhow::Result<ResourcesToml>,
|
||||
Vec<Log>,
|
||||
// commit short hash
|
||||
String,
|
||||
// commit message
|
||||
String,
|
||||
)> {
|
||||
let name = to_monitor_name(&sync.name);
|
||||
let clone_args: CloneArgs = sync.into();
|
||||
|
||||
let config = core_config();
|
||||
|
||||
let github_token = clone_args
|
||||
.github_account
|
||||
.as_ref()
|
||||
.map(|account| {
|
||||
config.github_accounts.get(account).ok_or_else(|| {
|
||||
anyhow!("did not find github token for account {account}")
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.cloned();
|
||||
|
||||
fs::create_dir_all(&config.sync_directory)
|
||||
.context("failed to create sync directory")?;
|
||||
|
||||
// lock simultaneous access to same directory
|
||||
let lock = resource_sync_lock_cache()
|
||||
.get_or_insert_default(&sync.id)
|
||||
.await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let mut logs =
|
||||
git::clone(clone_args, &config.sync_directory, github_token)
|
||||
.await
|
||||
.context("failed to clone resource repo")?;
|
||||
|
||||
let repo_dir = config.sync_directory.join(&name);
|
||||
let LatestCommit { hash, message } =
|
||||
git::get_commit_hash_info(&repo_dir)
|
||||
.await
|
||||
.context("failed to get commit hash info")?;
|
||||
|
||||
let repo_path = config.sync_directory.join(&sync.name);
|
||||
let resource_path = repo_path.join(&sync.config.resource_path);
|
||||
|
||||
let res = super::file::read_resources(&resource_path).map(
|
||||
|(resources, log)| {
|
||||
logs.push(log);
|
||||
resources
|
||||
},
|
||||
);
|
||||
|
||||
if let Err(e) = std::fs::remove_dir_all(&repo_path) {
|
||||
warn!("failed to remove sync repo directory | {e:?}")
|
||||
}
|
||||
|
||||
Ok((res, logs, hash, message))
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::write::{UpdateDescription, UpdateTagsOnResource},
|
||||
entities::{
|
||||
self,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
sync::SyncUpdate,
|
||||
tag::Tag,
|
||||
toml::ResourceToml,
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource::MonitorResource, state::State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub type ToUpdate<T> = Vec<ToUpdateItem<T>>;
|
||||
pub type ToCreate<T> = Vec<ResourceToml<T>>;
|
||||
/// Vec of resource names
|
||||
pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult<T> = (ToCreate<T>, ToUpdate<T>, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem<T: Default> {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<T>,
|
||||
pub update_description: bool,
|
||||
pub update_tags: bool,
|
||||
}
|
||||
|
||||
pub trait ResourceSync: MonitorResource + Sized {
|
||||
fn resource_target(id: String) -> ResourceTarget;
|
||||
|
||||
/// 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(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff>;
|
||||
|
||||
/// Apply any changes to computed config diff
|
||||
/// before logging
|
||||
fn validate_diff(_diff: &mut Self::ConfigDiff) {}
|
||||
|
||||
async fn run_updates(
|
||||
to_create: ToCreate<Self::PartialConfig>,
|
||||
to_update: ToUpdate<Self::PartialConfig>,
|
||||
to_delete: ToDelete,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log =
|
||||
format!("running updates on {}s", Self::resource_type());
|
||||
|
||||
for resource in to_create {
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
let id = match crate::resource::create::<Self>(
|
||||
&resource.name,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resource) => resource.id,
|
||||
Err(e) => {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
run_update_tags::<Self>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_description::<Self>(
|
||||
id,
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
|
||||
for ToUpdateItem {
|
||||
id,
|
||||
resource,
|
||||
update_description,
|
||||
update_tags,
|
||||
} in to_update
|
||||
{
|
||||
// Update resource
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
|
||||
if update_description {
|
||||
run_update_description::<Self>(
|
||||
id.clone(),
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if update_tags {
|
||||
run_update_tags::<Self>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if !resource.config.is_none() {
|
||||
if let Err(e) = crate::resource::update::<Self>(
|
||||
&id,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for resource in to_delete {
|
||||
if let Err(e) =
|
||||
crate::resource::delete::<Self>(&resource, sync_user()).await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&resource)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let stage = format!("Update {}s", Self::resource_type());
|
||||
Some(if has_error {
|
||||
Log::error(&stage, log)
|
||||
} else {
|
||||
Log::simple(&stage, log)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns all the diffs into a readable string
|
||||
pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
resources: Vec<ResourceToml<Resource::PartialConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(Resource::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get resources from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: format!("{} Updates", Resource::resource_type()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(resource.name.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for mut resource in resources {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First 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: Resource::Config = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Resource::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Resource::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Resource::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
update.to_update += 1;
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
if resource.description != original.description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
muted("to"),
|
||||
colored(&resource.description, "green")
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from = colored(&format!("{:?}", original_tags), "red");
|
||||
let to = colored(&format!("{:?}", resource.tags), "green");
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
muted("to"),
|
||||
));
|
||||
}
|
||||
lines.extend(diff.iter_field_diffs().map(
|
||||
|FieldDiff { field, from, to }| {
|
||||
format!(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&from, "red"),
|
||||
muted("to"),
|
||||
colored(&to, "green")
|
||||
)
|
||||
},
|
||||
));
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name),
|
||||
muted("description"),
|
||||
resource.description,
|
||||
muted("tags"),
|
||||
resource.tags,
|
||||
muted("config"),
|
||||
serde_json::to_string_pretty(&resource.config)
|
||||
.context("failed to serialize config to json")?
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
/// Gets all the resources to update. For use in sync execution.
|
||||
pub async fn get_updates_for_execution<Resource: ResourceSync>(
|
||||
resources: Vec<ResourceToml<Resource::PartialConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<UpdatesResult<Resource::PartialConfig>> {
|
||||
let map = find_collect(Resource::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get resources from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = ToCreate::<Resource::PartialConfig>::new();
|
||||
let mut to_update = ToUpdate::<Resource::PartialConfig>::new();
|
||||
let mut to_delete = ToDelete::new();
|
||||
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
to_delete.push(resource.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for mut resource in resources {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First 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: Resource::Config = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Resource::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Resource::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Resource::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Minimizes updates through diffing.
|
||||
resource.config = diff.into();
|
||||
|
||||
let update = ToUpdateItem {
|
||||
id: original.id.clone(),
|
||||
update_description: resource.description
|
||||
!= original.description,
|
||||
update_tags: resource.tags != original_tags,
|
||||
resource,
|
||||
};
|
||||
|
||||
to_update.push(update);
|
||||
}
|
||||
None => to_create.push(resource),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
id: String,
|
||||
name: &str,
|
||||
tags: Vec<String>,
|
||||
log: &mut String,
|
||||
has_error: &mut bool,
|
||||
) {
|
||||
// Update tags
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateTagsOnResource {
|
||||
target: Resource::resource_target(id),
|
||||
tags,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update tags on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' tags",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_update_description<Resource: ResourceSync>(
|
||||
id: String,
|
||||
name: &str,
|
||||
description: String,
|
||||
log: &mut String,
|
||||
has_error: &mut bool,
|
||||
) {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateDescription {
|
||||
target: Resource::resource_target(id.clone()),
|
||||
description,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update description on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllResourcesById {
|
||||
pub servers: HashMap<String, Server>,
|
||||
pub deployments: HashMap<String, Deployment>,
|
||||
pub builds: HashMap<String, Build>,
|
||||
pub repos: HashMap<String, Repo>,
|
||||
pub procedures: HashMap<String, Procedure>,
|
||||
pub builders: HashMap<String, Builder>,
|
||||
pub alerters: HashMap<String, Alerter>,
|
||||
pub templates: HashMap<String, ServerTemplate>,
|
||||
pub syncs: HashMap<String, entities::sync::ResourceSync>,
|
||||
}
|
||||
|
||||
impl AllResourcesById {
|
||||
pub async fn load() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
servers: crate::resource::get_id_to_resource_map::<Server>()
|
||||
.await?,
|
||||
deployments: crate::resource::get_id_to_resource_map::<
|
||||
Deployment,
|
||||
>()
|
||||
.await?,
|
||||
builds: crate::resource::get_id_to_resource_map::<Build>()
|
||||
.await?,
|
||||
repos: crate::resource::get_id_to_resource_map::<Repo>()
|
||||
.await?,
|
||||
procedures:
|
||||
crate::resource::get_id_to_resource_map::<Procedure>().await?,
|
||||
builders: crate::resource::get_id_to_resource_map::<Builder>()
|
||||
.await?,
|
||||
alerters: crate::resource::get_id_to_resource_map::<Alerter>()
|
||||
.await?,
|
||||
templates: crate::resource::get_id_to_resource_map::<
|
||||
ServerTemplate,
|
||||
>()
|
||||
.await?,
|
||||
syncs: crate::resource::get_id_to_resource_map::<
|
||||
entities::sync::ResourceSync,
|
||||
>()
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
self,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
helpers::sync::{
|
||||
bold, colored, muted,
|
||||
resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
},
|
||||
resource::MonitorResource,
|
||||
};
|
||||
|
||||
use super::resource::{
|
||||
AllResourcesById, ToCreate, ToDelete, ToUpdate,
|
||||
};
|
||||
|
||||
impl ResourceSync for Server {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Server(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Build {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Build(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
original.builder_id = resources
|
||||
.builders
|
||||
.get(&original.builder_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
|
||||
fn validate_diff(diff: &mut Self::ConfigDiff) {
|
||||
if let Some((_, to)) = &diff.version {
|
||||
if to.is_none() {
|
||||
diff.version = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Deployment {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace the server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// need to replace the build id with name
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: resources
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: version.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Repo {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Repo(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// Need to replace server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Alerter {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Alerter(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Builder {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Builder(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace server builder id with name
|
||||
if let BuilderConfig::Server(config) = &mut original {
|
||||
config.server_id = resources
|
||||
.servers
|
||||
.get(&config.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for ServerTemplate {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::ServerTemplate(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for entities::sync::ResourceSync {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Procedure {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Procedure(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
for stage in &mut original.stages {
|
||||
for execution in &mut stage.executions {
|
||||
match &mut execution.execution {
|
||||
Execution::None(_) => {}
|
||||
Execution::RunProcedure(config) => {
|
||||
config.procedure = resources
|
||||
.procedures
|
||||
.get(&config.procedure)
|
||||
.map(|p| p.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RunBuild(config) => {
|
||||
config.build = resources
|
||||
.builds
|
||||
.get(&config.build)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::Deploy(config) => {
|
||||
config.deployment = resources
|
||||
.deployments
|
||||
.get(&config.deployment)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::StartContainer(config) => {
|
||||
config.deployment = resources
|
||||
.deployments
|
||||
.get(&config.deployment)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::StopContainer(config) => {
|
||||
config.deployment = resources
|
||||
.deployments
|
||||
.get(&config.deployment)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RemoveContainer(config) => {
|
||||
config.deployment = resources
|
||||
.deployments
|
||||
.get(&config.deployment)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::CloneRepo(config) => {
|
||||
config.repo = resources
|
||||
.repos
|
||||
.get(&config.repo)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::PullRepo(config) => {
|
||||
config.repo = resources
|
||||
.repos
|
||||
.get(&config.repo)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::StopAllContainers(config) => {
|
||||
config.server = resources
|
||||
.servers
|
||||
.get(&config.server)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::PruneNetworks(config) => {
|
||||
config.server = resources
|
||||
.servers
|
||||
.get(&config.server)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::PruneImages(config) => {
|
||||
config.server = resources
|
||||
.servers
|
||||
.get(&config.server)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::PruneContainers(config) => {
|
||||
config.server = resources
|
||||
.servers
|
||||
.get(&config.server)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RunSync(config) => {
|
||||
config.sync = resources
|
||||
.syncs
|
||||
.get(&config.sync)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::Sleep(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
|
||||
async fn run_updates(
|
||||
mut to_create: ToCreate<Self::PartialConfig>,
|
||||
mut to_update: ToUpdate<Self::PartialConfig>,
|
||||
to_delete: ToDelete,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log =
|
||||
format!("running updates on {}s", Self::resource_type());
|
||||
|
||||
for name in to_delete {
|
||||
if let Err(e) =
|
||||
crate::resource::delete::<Procedure>(&name, sync_user()).await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if to_update.is_empty() && to_create.is_empty() {
|
||||
let stage = "Update Procedures";
|
||||
return Some(if has_error {
|
||||
Log::error(stage, log)
|
||||
} else {
|
||||
Log::simple(stage, log)
|
||||
});
|
||||
}
|
||||
|
||||
for i in 0..10 {
|
||||
let mut to_pull = Vec::new();
|
||||
for ToUpdateItem {
|
||||
id,
|
||||
resource,
|
||||
update_description,
|
||||
update_tags,
|
||||
} in &to_update
|
||||
{
|
||||
// Update resource
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
if *update_description {
|
||||
run_update_description::<Procedure>(
|
||||
id.clone(),
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if *update_tags {
|
||||
run_update_tags::<Procedure>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if !resource.config.is_none() {
|
||||
if let Err(e) = crate::resource::update::<Procedure>(
|
||||
id,
|
||||
resource.config.clone(),
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if i == 9 {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} '{}' updated",
|
||||
muted("INFO"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
// have to clone out so to_update is mutable
|
||||
to_pull.push(id.clone());
|
||||
}
|
||||
//
|
||||
to_update.retain(|resource| !to_pull.contains(&resource.id));
|
||||
|
||||
let mut to_pull = Vec::new();
|
||||
for resource in &to_create {
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
let id = match crate::resource::create::<Procedure>(
|
||||
&name,
|
||||
resource.config.clone(),
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resource) => resource.id,
|
||||
Err(e) => {
|
||||
if i == 9 {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
run_update_tags::<Procedure>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_description::<Procedure>(
|
||||
id,
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
to_pull.push(name);
|
||||
}
|
||||
to_create.retain(|resource| !to_pull.contains(&resource.name));
|
||||
|
||||
if to_update.is_empty() && to_create.is_empty() {
|
||||
let stage = "Update Procedures";
|
||||
return Some(if has_error {
|
||||
Log::error(stage, log)
|
||||
} else {
|
||||
Log::simple(stage, log)
|
||||
});
|
||||
}
|
||||
}
|
||||
warn!("procedure sync loop exited after max iterations");
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,651 @@
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::{
|
||||
read::ListUserTargetPermissions,
|
||||
write::{
|
||||
CreateUserGroup, DeleteUserGroup, SetUsersInUserGroup,
|
||||
UpdatePermissionOnTarget,
|
||||
},
|
||||
},
|
||||
entities::{
|
||||
permission::UserTarget,
|
||||
sync::SyncUpdate,
|
||||
toml::{PermissionToml, UserGroupToml},
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted, resource::AllResourcesById};
|
||||
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
update_users: bool,
|
||||
update_permissions: bool,
|
||||
}
|
||||
|
||||
pub struct DeleteItem {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_view(
|
||||
user_groups: Vec<UserGroupToml>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(&db_client().await.user_groups, None, None)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.into_iter()
|
||||
.map(|ug| (ug.name.clone(), ug))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: String::from("User Group Updates"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for user_group in map.values() {
|
||||
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(user_group.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id_to_user = find_collect(&db_client().await.users, None, None)
|
||||
.await
|
||||
.context("failed to query db for Users")?
|
||||
.into_iter()
|
||||
.map(|user| (user.id.clone(), user))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
let original = match map.get(&user_group.name).cloned() {
|
||||
Some(original) => original,
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:?}\n{}: {:?}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&user_group.name, "green"),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
user_group.permissions,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut original_users = original
|
||||
.users
|
||||
.into_iter()
|
||||
.filter_map(|user_id| {
|
||||
id_to_user.get(&user_id).map(|u| u.username.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut original_permissions = State
|
||||
.resolve(
|
||||
ListUserTargetPermissions {
|
||||
user_target: UserTarget::UserGroup(original.id),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
ResourceTarget::System(_) => {}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
*id = all_resources
|
||||
.servers
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
*id = all_resources
|
||||
.procedures
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all_resources
|
||||
.templates
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = all_resources
|
||||
.syncs
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
PermissionToml {
|
||||
target: p.resource_target,
|
||||
level: p.level,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
user_group.permissions.sort_by(sort_permissions);
|
||||
original_permissions.sort_by(sort_permissions);
|
||||
|
||||
let update_users = user_group.users != original_users;
|
||||
let update_permissions =
|
||||
user_group.permissions != original_permissions;
|
||||
|
||||
// only add log after diff detected
|
||||
if update_users || update_permissions {
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
bold(&user_group.name),
|
||||
));
|
||||
let mut lines = Vec::<String>::new();
|
||||
if update_users {
|
||||
let adding = user_group
|
||||
.users
|
||||
.iter()
|
||||
.filter(|user| !original_users.contains(user))
|
||||
.map(|user| user.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
};
|
||||
let removing = original_users
|
||||
.iter()
|
||||
.filter(|user| !user_group.users.contains(user))
|
||||
.map(|user| user.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'users'\n{}: {removing}\n{}: {adding}",
|
||||
muted("field"),
|
||||
muted("removing"),
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
if update_permissions {
|
||||
let adding = user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!original_permissions.contains(permission)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
};
|
||||
let removing = original_permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!user_group.permissions.contains(permission)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {adding}",
|
||||
muted("field"),
|
||||
muted("removing"),
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
}
|
||||
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_execution(
|
||||
user_groups: Vec<UserGroupToml>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<(
|
||||
Vec<UserGroupToml>,
|
||||
Vec<UpdateItem>,
|
||||
Vec<DeleteItem>,
|
||||
)> {
|
||||
let map = find_collect(&db_client().await.user_groups, None, None)
|
||||
.await
|
||||
.context("failed to query db for UserGroups")?
|
||||
.into_iter()
|
||||
.map(|ug| (ug.name.clone(), ug))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = Vec::<UserGroupToml>::new();
|
||||
let mut to_update = Vec::<UpdateItem>::new();
|
||||
let mut to_delete = Vec::<DeleteItem>::new();
|
||||
|
||||
if delete {
|
||||
for user_group in map.values() {
|
||||
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
|
||||
to_delete.push(DeleteItem {
|
||||
id: user_group.id.clone(),
|
||||
name: user_group.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user_groups.is_empty() {
|
||||
return Ok((to_create, to_update, to_delete));
|
||||
}
|
||||
|
||||
let id_to_user = find_collect(&db_client().await.users, None, None)
|
||||
.await
|
||||
.context("failed to query db for Users")?
|
||||
.into_iter()
|
||||
.map(|user| (user.id.clone(), user))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
let original = match map.get(&user_group.name).cloned() {
|
||||
Some(original) => original,
|
||||
None => {
|
||||
to_create.push(user_group);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut original_users = original
|
||||
.users
|
||||
.into_iter()
|
||||
.filter_map(|user_id| {
|
||||
id_to_user.get(&user_id).map(|u| u.username.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut original_permissions = State
|
||||
.resolve(
|
||||
ListUserTargetPermissions {
|
||||
user_target: UserTarget::UserGroup(original.id),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
ResourceTarget::System(_) => {}
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = all_resources
|
||||
.builds
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = all_resources
|
||||
.builders
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id = all_resources
|
||||
.deployments
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
*id = all_resources
|
||||
.servers
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = all_resources
|
||||
.repos
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = all_resources
|
||||
.alerters
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
*id = all_resources
|
||||
.procedures
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all_resources
|
||||
.templates
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = all_resources
|
||||
.syncs
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
PermissionToml {
|
||||
target: p.resource_target,
|
||||
level: p.level,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
user_group.permissions.sort_by(sort_permissions);
|
||||
original_permissions.sort_by(sort_permissions);
|
||||
|
||||
let update_users = user_group.users != original_users;
|
||||
let update_permissions =
|
||||
user_group.permissions != original_permissions;
|
||||
|
||||
// only push update after failed diff
|
||||
if update_users || update_permissions {
|
||||
to_update.push(UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
/// order permissions in deterministic way
|
||||
fn sort_permissions(
|
||||
a: &PermissionToml,
|
||||
b: &PermissionToml,
|
||||
) -> Ordering {
|
||||
let (a_t, a_id) = a.target.extract_variant_id();
|
||||
let (b_t, b_id) = b.target.extract_variant_id();
|
||||
match (a_t.cmp(&b_t), a_id.cmp(b_id)) {
|
||||
(Ordering::Greater, _) => Ordering::Greater,
|
||||
(Ordering::Less, _) => Ordering::Less,
|
||||
(_, Ordering::Greater) => Ordering::Greater,
|
||||
(_, Ordering::Less) => Ordering::Less,
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: Vec<UserGroupToml>,
|
||||
to_update: Vec<UpdateItem>,
|
||||
to_delete: Vec<DeleteItem>,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log = String::from("running updates on UserGroups");
|
||||
|
||||
// Create the non-existant user groups
|
||||
for user_group in to_create {
|
||||
// Create the user group
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
CreateUserGroup {
|
||||
name: user_group.name.clone(),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group.name)
|
||||
));
|
||||
continue;
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
};
|
||||
|
||||
set_users(
|
||||
user_group.name.clone(),
|
||||
user_group.users,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Update the existing user groups
|
||||
for UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
} in to_update
|
||||
{
|
||||
if update_users {
|
||||
set_users(
|
||||
user_group.name.clone(),
|
||||
user_group.users,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if update_permissions {
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
for user_group in to_delete {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
DeleteUserGroup { id: user_group.id },
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let stage = "Update UserGroups";
|
||||
Some(if has_error {
|
||||
Log::error(stage, log)
|
||||
} else {
|
||||
Log::simple(stage, log)
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_users(
|
||||
user_group: String,
|
||||
users: Vec<String>,
|
||||
log: &mut String,
|
||||
has_error: &mut bool,
|
||||
) {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
SetUsersInUserGroup {
|
||||
user_group: user_group.clone(),
|
||||
users,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set users in group {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' users",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_update_permissions(
|
||||
user_group: String,
|
||||
permissions: Vec<PermissionToml>,
|
||||
log: &mut String,
|
||||
has_error: &mut bool,
|
||||
) {
|
||||
for PermissionToml { target, level } in permissions {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdatePermissionOnTarget {
|
||||
user_target: UserTarget::UserGroup(user_group.clone()),
|
||||
resource_target: target.clone(),
|
||||
permission: level,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set permssion in group {} | target: {target:?} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' permissions",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateVariable, DeleteVariable, UpdateVariableDescription,
|
||||
UpdateVariableValue,
|
||||
},
|
||||
entities::{
|
||||
sync::SyncUpdate, update::Log, user::sync_user,
|
||||
variable::Variable,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub variable: Variable,
|
||||
pub update_value: bool,
|
||||
pub update_description: bool,
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_view(
|
||||
variables: Vec<Variable>,
|
||||
delete: bool,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(&db_client().await.variables, None, None)
|
||||
.await
|
||||
.context("failed to query db for variables")?
|
||||
.into_iter()
|
||||
.map(|v| (v.name.clone(), v))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: String::from("Variable Updates"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for variable in map.values() {
|
||||
if !variables.iter().any(|v| v.name == variable.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(variable.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for variable in variables {
|
||||
match map.get(&variable.name) {
|
||||
Some(original) => {
|
||||
let item = ToUpdateItem {
|
||||
update_value: original.value != variable.value,
|
||||
update_description: original.description
|
||||
!= variable.description,
|
||||
variable,
|
||||
};
|
||||
if !item.update_value && !item.update_description {
|
||||
continue;
|
||||
}
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
bold(&item.variable.name),
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
|
||||
if item.update_value {
|
||||
lines.push(format!(
|
||||
"{}: 'value'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.value, "red"),
|
||||
muted("to"),
|
||||
colored(&item.variable.value, "green")
|
||||
))
|
||||
}
|
||||
|
||||
if item.update_description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
muted("to"),
|
||||
colored(&item.variable.description, "green")
|
||||
))
|
||||
}
|
||||
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&variable.name, "green"),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
pub async fn get_updates_for_execution(
|
||||
variables: Vec<Variable>,
|
||||
delete: bool,
|
||||
) -> anyhow::Result<(Vec<Variable>, Vec<ToUpdateItem>, Vec<String>)> {
|
||||
let map = find_collect(&db_client().await.variables, None, None)
|
||||
.await
|
||||
.context("failed to query db for variables")?
|
||||
.into_iter()
|
||||
.map(|v| (v.name.clone(), v))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = Vec::<Variable>::new();
|
||||
let mut to_update = Vec::<ToUpdateItem>::new();
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
|
||||
if delete {
|
||||
for variable in map.values() {
|
||||
if !variables.iter().any(|v| v.name == variable.name) {
|
||||
to_delete.push(variable.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for variable in variables {
|
||||
match map.get(&variable.name) {
|
||||
Some(original) => {
|
||||
let item = ToUpdateItem {
|
||||
update_value: original.value != variable.value,
|
||||
update_description: original.description
|
||||
!= variable.description,
|
||||
variable,
|
||||
};
|
||||
if !item.update_value && !item.update_description {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_update.push(item);
|
||||
}
|
||||
None => to_create.push(variable),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: Vec<Variable>,
|
||||
to_update: Vec<ToUpdateItem>,
|
||||
to_delete: Vec<String>,
|
||||
) -> Option<Log> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log = String::from("running updates on Variables");
|
||||
|
||||
for variable in to_create {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
CreateVariable {
|
||||
name: variable.name.clone(),
|
||||
value: variable.value,
|
||||
description: variable.description,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
for ToUpdateItem {
|
||||
variable,
|
||||
update_value,
|
||||
update_description,
|
||||
} in to_update
|
||||
{
|
||||
if update_value {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateVariableValue {
|
||||
name: variable.name.clone(),
|
||||
value: variable.value,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable value for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' value",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
if update_description {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdateVariableDescription {
|
||||
name: variable.name.clone(),
|
||||
description: variable.description,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable description for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for variable in to_delete {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
DeleteVariable {
|
||||
name: variable.clone(),
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
bold(&variable)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
bold(&variable)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let stage = "Update Variables";
|
||||
Some(if has_error {
|
||||
Log::error(stage, log)
|
||||
} else {
|
||||
Log::simple(stage, log)
|
||||
})
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use monitor_client::entities::{
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
sync::ResourceSync,
|
||||
update::{ResourceTarget, Update, UpdateListItem},
|
||||
user::User,
|
||||
Operation,
|
||||
@@ -205,6 +206,14 @@ pub async fn init_execution_update(
|
||||
.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Resource Sync
|
||||
ExecuteRequest::RunSync(data) => (
|
||||
Operation::RunSync,
|
||||
ResourceTarget::ResourceSync(
|
||||
resource::get::<ResourceSync>(&data.sync).await?.id,
|
||||
),
|
||||
),
|
||||
};
|
||||
let mut update = make_update(target, operation, user);
|
||||
update.in_progress();
|
||||
|
||||
@@ -5,9 +5,10 @@ use axum::{extract::Path, http::HeaderMap, routing::post, Router};
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use monitor_client::{
|
||||
api::execute,
|
||||
api::{execute, write::RefreshResourceSyncPending},
|
||||
entities::{
|
||||
build::Build, procedure::Procedure, repo::Repo, user::github_user,
|
||||
build::Build, procedure::Procedure, repo::Repo,
|
||||
sync::ResourceSync, user::github_user,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
@@ -117,6 +118,50 @@ pub fn router() -> Router {
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/refresh",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_refresh_webhook", id);
|
||||
async {
|
||||
let res = handle_sync_refresh_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
.route(
|
||||
"/sync/:id/sync",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_execute_webhook", id);
|
||||
async {
|
||||
let res = handle_sync_execute_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run sync webook for sync {id} | {e:#}");
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_build_webhook(
|
||||
@@ -253,6 +298,66 @@ async fn handle_procedure_webhook(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_sync_refresh_webhook(
|
||||
sync_id: String,
|
||||
headers: HeaderMap,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// 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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
State
|
||||
.resolve(RefreshResourceSyncPending { sync: sync_id }, user)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_sync_execute_webhook(
|
||||
sync_id: String,
|
||||
headers: HeaderMap,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// 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 = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
verify_gh_signature(headers, &body).await?;
|
||||
let request_branch = extract_branch(&body)?;
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
if request_branch != sync.config.branch {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
let user = github_user().to_owned();
|
||||
let req =
|
||||
crate::api::execute::ExecuteRequest::RunSync(execute::RunSync {
|
||||
sync: sync_id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::RunSync(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user, update)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn verify_gh_signature(
|
||||
headers: HeaderMap,
|
||||
@@ -313,3 +418,8 @@ fn procedure_locks() -> &'static ListenerLockCache {
|
||||
static BUILD_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
fn sync_locks() -> &'static ListenerLockCache {
|
||||
static SYNC_LOCKS: OnceLock<ListenerLockCache> = OnceLock::new();
|
||||
SYNC_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ async fn app() -> anyhow::Result<()> {
|
||||
// Spawn monitoring loops
|
||||
monitor::spawn_monitor_loop()?;
|
||||
helpers::prune::spawn_prune_loop();
|
||||
helpers::sync::spawn_sync_refresh_loop();
|
||||
resource::spawn_build_state_refresh_loop();
|
||||
resource::spawn_repo_state_refresh_loop();
|
||||
resource::spawn_procedure_state_refresh_loop();
|
||||
resource::spawn_resource_sync_state_refresh_loop();
|
||||
|
||||
// Setup static frontend services
|
||||
let frontend_path = frontend_path();
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use monitor_client::entities::{
|
||||
alert::{Alert, AlertData},
|
||||
deployment::Deployment,
|
||||
deployment::{Deployment, DeploymentState},
|
||||
server::stats::SeverityLevel,
|
||||
update::ResourceTarget,
|
||||
};
|
||||
@@ -18,30 +18,45 @@ pub async fn alert_deployments(
|
||||
server_names: HashMap<String, String>,
|
||||
) {
|
||||
let mut alerts = Vec::<Alert>::new();
|
||||
for v in deployment_status_cache().get_list().await {
|
||||
if v.prev.is_none() {
|
||||
for status in deployment_status_cache().get_list().await {
|
||||
// Don't alert if prev None
|
||||
let Some(prev) = status.prev else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Don't alert if either prev or curr is Unknown.
|
||||
// This will happen if server is unreachable, so this would be redundant.
|
||||
if status.curr.state == DeploymentState::Unknown
|
||||
|| prev == DeploymentState::Unknown
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let prev = v.prev.as_ref().unwrap().to_owned();
|
||||
if v.curr.state != prev {
|
||||
|
||||
if status.curr.state != prev {
|
||||
// send alert
|
||||
let d = resource::get::<Deployment>(&v.curr.id).await;
|
||||
if let Err(e) = d {
|
||||
error!("failed to get deployment from db | {e:#?}");
|
||||
let Ok(deployment) =
|
||||
resource::get::<Deployment>(&status.curr.id)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!("failed to get deployment from db | {e:#?}")
|
||||
})
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !deployment.config.send_alerts {
|
||||
continue;
|
||||
}
|
||||
let d = d.unwrap();
|
||||
let target: ResourceTarget = (&d).into();
|
||||
let target: ResourceTarget = (&deployment).into();
|
||||
let data = AlertData::ContainerStateChange {
|
||||
id: v.curr.id.clone(),
|
||||
name: d.name,
|
||||
id: status.curr.id.clone(),
|
||||
name: deployment.name,
|
||||
server_name: server_names
|
||||
.get(&d.config.server_id)
|
||||
.get(&deployment.config.server_id)
|
||||
.cloned()
|
||||
.unwrap_or(String::from("unknown")),
|
||||
server_id: d.config.server_id,
|
||||
server_id: deployment.config.server_id,
|
||||
from: prev,
|
||||
to: v.curr.state,
|
||||
to: status.curr.state,
|
||||
};
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
@@ -52,9 +67,7 @@ pub async fn alert_deployments(
|
||||
data,
|
||||
ts,
|
||||
};
|
||||
if d.config.send_alerts {
|
||||
alerts.push(alert);
|
||||
}
|
||||
alerts.push(alert);
|
||||
}
|
||||
}
|
||||
if alerts.is_empty() {
|
||||
|
||||
@@ -16,11 +16,11 @@ use monitor_client::entities::{
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOneOptions, Collection},
|
||||
mongodb::{bson::doc, options::FindOptions, Collection},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helpers::empty_or_only_spaces,
|
||||
helpers::{empty_or_only_spaces, query::get_latest_update},
|
||||
state::{action_states, build_state_cache, db_client},
|
||||
};
|
||||
|
||||
@@ -205,28 +205,40 @@ async fn get_build_state(id: &String) -> BuildState {
|
||||
|
||||
async fn get_build_state_from_db(id: &str) -> BuildState {
|
||||
async {
|
||||
let state = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "Build",
|
||||
"target.id": id,
|
||||
"operation": "RunBuild"
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
let state = match tokio::try_join!(
|
||||
latest_2_build_updates(id),
|
||||
get_latest_update(
|
||||
ResourceTargetVariant::Build,
|
||||
id,
|
||||
Operation::CancelBuild
|
||||
),
|
||||
)? {
|
||||
([Some(build), second], Some(cancel))
|
||||
if cancel.start_ts > build.start_ts =>
|
||||
{
|
||||
match second {
|
||||
Some(build) => {
|
||||
if build.success {
|
||||
BuildState::Ok
|
||||
} else {
|
||||
BuildState::Failed
|
||||
}
|
||||
}
|
||||
None => BuildState::Ok,
|
||||
}
|
||||
}
|
||||
([Some(build), _], _) => {
|
||||
if build.success {
|
||||
BuildState::Ok
|
||||
} else {
|
||||
BuildState::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(BuildState::Ok);
|
||||
}
|
||||
_ => {
|
||||
// No build update ever, should be fine
|
||||
BuildState::Ok
|
||||
}
|
||||
};
|
||||
anyhow::Ok(state)
|
||||
}
|
||||
.await
|
||||
@@ -235,3 +247,25 @@ async fn get_build_state_from_db(id: &str) -> BuildState {
|
||||
})
|
||||
.unwrap_or(BuildState::Unknown)
|
||||
}
|
||||
|
||||
async fn latest_2_build_updates(
|
||||
id: &str,
|
||||
) -> anyhow::Result<[Option<Update>; 2]> {
|
||||
let mut builds = find_collect(
|
||||
&db_client().await.updates,
|
||||
doc! {
|
||||
"target.type": "Build",
|
||||
"target.id": id,
|
||||
"operation": "RunBuild"
|
||||
},
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.limit(2)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to query for latest updates")?;
|
||||
let second = builds.pop();
|
||||
let first = builds.pop();
|
||||
Ok([first, second])
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use monitor_client::entities::{
|
||||
};
|
||||
use mungos::mongodb::Collection;
|
||||
use periphery_client::api::container::RemoveContainer;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
@@ -177,42 +178,64 @@ impl super::MonitorResource for Deployment {
|
||||
DeploymentState::NotDeployed | DeploymentState::Unknown
|
||||
) {
|
||||
// container needs to be destroyed
|
||||
let server =
|
||||
super::get::<Server>(&deployment.config.server_id).await;
|
||||
if let Err(e) = server {
|
||||
update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to retrieve server at {} from db | {e:#?}",
|
||||
deployment.config.server_id
|
||||
),
|
||||
);
|
||||
} else if let Ok(server) = server {
|
||||
match periphery_client(&server) {
|
||||
Ok(periphery) => match periphery
|
||||
.request(RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container on periphery | {e:#?}"
|
||||
),
|
||||
),
|
||||
},
|
||||
Err(e) => update.push_error_log(
|
||||
let server = match super::get::<Server>(
|
||||
&deployment.config.server_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container on periphery | {e:#?}"
|
||||
"failed to retrieve server at {} from db.\n\nerror: {}",
|
||||
deployment.config.server_id,
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
),
|
||||
};
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if !server.config.enabled {
|
||||
// Don't need to
|
||||
update.push_simple_log(
|
||||
"remove container",
|
||||
"skipping container removal, server is disabled.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let periphery = match periphery_client(&server) {
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
// This case won't ever happen, as periphery_client only fallible if the server is disabled.
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container on periphery.\n\nerror: {}",
|
||||
serialize_error_pretty(&e),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match periphery
|
||||
.request(RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to remove container.\n\nerror: {}",
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::str::FromStr;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use futures::future::join_all;
|
||||
@@ -19,6 +19,7 @@ use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{
|
||||
bson::{doc, oid::ObjectId, to_document, Document},
|
||||
options::FindOptions,
|
||||
Collection,
|
||||
},
|
||||
};
|
||||
@@ -48,6 +49,7 @@ mod procedure;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod sync;
|
||||
|
||||
pub use build::{
|
||||
refresh_build_state_cache, spawn_build_state_refresh_loop,
|
||||
@@ -58,18 +60,28 @@ pub use procedure::{
|
||||
pub use repo::{
|
||||
refresh_repo_state_cache, spawn_repo_state_refresh_loop,
|
||||
};
|
||||
pub use sync::{
|
||||
refresh_resource_sync_state_cache,
|
||||
spawn_resource_sync_state_refresh_loop,
|
||||
};
|
||||
|
||||
/// Implement on each monitor resource for common methods
|
||||
pub trait MonitorResource {
|
||||
type ListItem: Serialize + Send;
|
||||
type Config: Send
|
||||
type Config: Clone
|
||||
+ Default
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Unpin
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ From<Self::PartialConfig>
|
||||
+ PartialDiff<Self::PartialConfig, Self::ConfigDiff>
|
||||
+ 'static;
|
||||
type PartialConfig: Into<Self::Config> + Serialize;
|
||||
type PartialConfig: Default
|
||||
+ From<Self::Config>
|
||||
+ Serialize
|
||||
+ MaybeNone;
|
||||
type ConfigDiff: Into<Self::PartialConfig>
|
||||
+ Serialize
|
||||
+ Diff
|
||||
@@ -262,11 +274,28 @@ async fn list_full_for_user_using_document<T: MonitorResource>(
|
||||
.collect::<Vec<_>>();
|
||||
filters.insert("_id", doc! { "$in": ids });
|
||||
}
|
||||
find_collect(T::coll().await, filters, None)
|
||||
find_collect(
|
||||
T::coll().await,
|
||||
filters,
|
||||
FindOptions::builder().sort(doc! { "name": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_id_to_resource_map<T: MonitorResource>(
|
||||
) -> anyhow::Result<HashMap<String, Resource<T::Config, T::Info>>> {
|
||||
let res = find_collect(T::coll().await, None, None)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to pull {}s from mongo", T::resource_type())
|
||||
})
|
||||
})?
|
||||
.into_iter()
|
||||
.map(|r| (r.id.clone(), r))
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// =======
|
||||
@@ -436,6 +465,9 @@ fn resource_target<T: MonitorResource>(id: String) -> ResourceTarget {
|
||||
ResourceTargetVariant::ServerTemplate => {
|
||||
ResourceTarget::ServerTemplate(id)
|
||||
}
|
||||
ResourceTargetVariant::ResourceSync => {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use monitor_client::{
|
||||
repo::Repo,
|
||||
resource::Resource,
|
||||
server::Server,
|
||||
sync::ResourceSync,
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::User,
|
||||
Operation,
|
||||
@@ -270,6 +271,16 @@ async fn validate_config(
|
||||
.await?;
|
||||
params.server = server.id;
|
||||
}
|
||||
Execution::RunSync(params) => {
|
||||
let sync = super::get_check_permissions::<ResourceSync>(
|
||||
¶ms.sync,
|
||||
user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
params.sync = sync.id;
|
||||
}
|
||||
Execution::Sleep(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::entities::{
|
||||
resource::Resource,
|
||||
sync::{
|
||||
PartialResourceSyncConfig, PendingSyncUpdatesData, ResourceSync,
|
||||
ResourceSyncConfig, ResourceSyncConfigDiff, ResourceSyncInfo,
|
||||
ResourceSyncListItem, ResourceSyncListItemInfo,
|
||||
ResourceSyncQuerySpecifics, ResourceSyncState,
|
||||
},
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::User,
|
||||
Operation,
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{options::FindOneOptions, Collection},
|
||||
};
|
||||
|
||||
use crate::state::{
|
||||
action_states, db_client, resource_sync_state_cache,
|
||||
};
|
||||
|
||||
impl super::MonitorResource for ResourceSync {
|
||||
type Config = ResourceSyncConfig;
|
||||
type PartialConfig = PartialResourceSyncConfig;
|
||||
type ConfigDiff = ResourceSyncConfigDiff;
|
||||
type Info = ResourceSyncInfo;
|
||||
type ListItem = ResourceSyncListItem;
|
||||
type QuerySpecifics = ResourceSyncQuerySpecifics;
|
||||
|
||||
fn resource_type() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::ResourceSync
|
||||
}
|
||||
|
||||
async fn coll(
|
||||
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
|
||||
&db_client().await.resource_syncs
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
resource_sync: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let state = get_resource_sync_state(
|
||||
&resource_sync.id,
|
||||
&resource_sync.info.pending.data,
|
||||
)
|
||||
.await;
|
||||
ResourceSyncListItem {
|
||||
id: resource_sync.id,
|
||||
name: resource_sync.name,
|
||||
tags: resource_sync.tags,
|
||||
resource_type: ResourceTargetVariant::ResourceSync,
|
||||
info: ResourceSyncListItemInfo {
|
||||
repo: resource_sync.config.repo,
|
||||
branch: resource_sync.config.branch,
|
||||
last_sync_ts: resource_sync.info.last_sync_ts,
|
||||
last_sync_hash: resource_sync.info.last_sync_hash,
|
||||
last_sync_message: resource_sync.info.last_sync_message,
|
||||
state,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn busy(id: &String) -> anyhow::Result<bool> {
|
||||
action_states()
|
||||
.resource_sync
|
||||
.get(id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.busy()
|
||||
}
|
||||
|
||||
// CREATE
|
||||
|
||||
fn create_operation() -> Operation {
|
||||
Operation::CreateResourceSync
|
||||
}
|
||||
|
||||
fn user_can_create(user: &User) -> bool {
|
||||
user.admin
|
||||
}
|
||||
|
||||
async fn validate_create_config(
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_create(
|
||||
_created: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
fn update_operation() -> Operation {
|
||||
Operation::UpdateResourceSync
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_id: &str,
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
_updated: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
fn delete_operation() -> Operation {
|
||||
Operation::DeleteRepo
|
||||
}
|
||||
|
||||
async fn pre_delete(
|
||||
_resource: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_delete(
|
||||
_resource: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_resource_sync_state_refresh_loop() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
refresh_resource_sync_state_cache().await;
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn refresh_resource_sync_state_cache() {
|
||||
let _ = async {
|
||||
let resource_syncs =
|
||||
find_collect(&db_client().await.resource_syncs, None, None)
|
||||
.await
|
||||
.context("failed to get resource_syncs from db")?;
|
||||
let cache = resource_sync_state_cache();
|
||||
for resource_sync in resource_syncs {
|
||||
let state =
|
||||
get_resource_sync_state_from_db(&resource_sync.id).await;
|
||||
cache.insert(resource_sync.id, state).await;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!("failed to refresh resource_sync state cache | {e:#}")
|
||||
});
|
||||
}
|
||||
|
||||
async fn get_resource_sync_state(
|
||||
id: &String,
|
||||
data: &PendingSyncUpdatesData,
|
||||
) -> ResourceSyncState {
|
||||
if let Some(state) = action_states()
|
||||
.resource_sync
|
||||
.get(id)
|
||||
.await
|
||||
.and_then(|s| {
|
||||
s.get()
|
||||
.map(|s| {
|
||||
if s.syncing {
|
||||
Some(ResourceSyncState::Syncing)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
let data = match data {
|
||||
PendingSyncUpdatesData::Err(_) => {
|
||||
return ResourceSyncState::Failed
|
||||
}
|
||||
PendingSyncUpdatesData::Ok(data) => data,
|
||||
};
|
||||
if !data.no_updates() {
|
||||
return ResourceSyncState::Pending;
|
||||
}
|
||||
resource_sync_state_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn get_resource_sync_state_from_db(
|
||||
id: &str,
|
||||
) -> ResourceSyncState {
|
||||
async {
|
||||
let state = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": id,
|
||||
"operation": "RunSync"
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
ResourceSyncState::Ok
|
||||
} else {
|
||||
ResourceSyncState::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(ResourceSyncState::Ok);
|
||||
anyhow::Ok(state)
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"failed to get resource sync state from db for {id} | {e:#}"
|
||||
)
|
||||
})
|
||||
.unwrap_or(ResourceSyncState::Unknown)
|
||||
}
|
||||
+19
-1
@@ -3,8 +3,9 @@ use std::sync::{Arc, OnceLock};
|
||||
use monitor_client::entities::{
|
||||
build::BuildState, deployment::DeploymentState,
|
||||
procedure::ProcedureState, repo::RepoState,
|
||||
sync::ResourceSyncState,
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
|
||||
use crate::{
|
||||
auth::jwt::JwtClient,
|
||||
@@ -89,3 +90,20 @@ pub fn procedure_state_cache() -> &'static ProcedureStateCache {
|
||||
OnceLock::new();
|
||||
PROCEDURE_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type ResourceSyncStateCache = Cache<String, ResourceSyncState>;
|
||||
|
||||
pub fn resource_sync_state_cache() -> &'static ResourceSyncStateCache
|
||||
{
|
||||
static RESOURCE_SYNC_STATE_CACHE: OnceLock<ResourceSyncStateCache> =
|
||||
OnceLock::new();
|
||||
RESOURCE_SYNC_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type ResourceSyncLockCache = Cache<String, Arc<Mutex<()>>>;
|
||||
|
||||
pub fn resource_sync_lock_cache() -> &'static ResourceSyncLockCache {
|
||||
static RESOURCE_SYNC_LOCK_CACHE: OnceLock<ResourceSyncLockCache> =
|
||||
OnceLock::new();
|
||||
RESOURCE_SYNC_LOCK_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod v0;
|
||||
pub mod v1_6;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::build::{BuildConfig, BuildInfo};
|
||||
use monitor_client::entities::build::{
|
||||
BuildConfig, BuildInfo, CloudRegistryConfig, ImageRegistry,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -213,10 +215,14 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
|
||||
repo: value.repo.unwrap_or_default(),
|
||||
branch: value.branch.unwrap_or_default(),
|
||||
github_account: value.github_account.unwrap_or_default(),
|
||||
docker_account: value.docker_account.unwrap_or_default(),
|
||||
docker_organization: value
|
||||
.docker_organization
|
||||
.unwrap_or_default(),
|
||||
image_registry: ImageRegistry::DockerHub(
|
||||
CloudRegistryConfig {
|
||||
account: value.docker_account.unwrap_or_default(),
|
||||
organization: value
|
||||
.docker_organization
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
pre_build: value
|
||||
.pre_build
|
||||
.map(|command| monitor_client::entities::SystemCommand {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
NoData,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -374,10 +378,15 @@ impl TryFrom<Deployment>
|
||||
.post_image
|
||||
.unwrap_or_default(),
|
||||
extra_args: value.docker_run_args.extra_args,
|
||||
docker_account: value
|
||||
.docker_run_args
|
||||
.docker_account
|
||||
.unwrap_or_default(),
|
||||
image_registry: match value.docker_run_args.docker_account {
|
||||
Some(account) => {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
None => ImageRegistry::None(NoData {}),
|
||||
},
|
||||
labels: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
NoData,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
resource::Resource, EnvironmentVar, SystemCommand, Version,
|
||||
};
|
||||
|
||||
pub type Build = Resource<BuildConfig, BuildInfo>;
|
||||
|
||||
impl From<Build> for monitor_client::entities::build::Build {
|
||||
fn from(value: Build) -> Self {
|
||||
monitor_client::entities::build::Build {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
updated_at: value.updated_at,
|
||||
tags: value.tags,
|
||||
info: monitor_client::entities::build::BuildInfo {
|
||||
last_built_at: value.info.last_built_at,
|
||||
},
|
||||
config: value.config.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct BuildInfo {
|
||||
pub last_built_at: i64,
|
||||
}
|
||||
|
||||
/// The build configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
/// Which builder is used to build the image.
|
||||
#[serde(default, alias = "builder")]
|
||||
pub builder_id: String,
|
||||
|
||||
/// Whether to skip secret interpolation in the build_args.
|
||||
#[serde(default)]
|
||||
pub skip_secret_interp: bool,
|
||||
|
||||
/// The current version of the build.
|
||||
#[serde(default)]
|
||||
pub version: Version,
|
||||
|
||||
/// The Github repo used as the source of the build.
|
||||
#[serde(default)]
|
||||
pub repo: String,
|
||||
|
||||
/// The branch of the repo.
|
||||
#[serde(default = "default_branch")]
|
||||
pub branch: String,
|
||||
|
||||
/// Optionally set a specific commit hash.
|
||||
#[serde(default)]
|
||||
pub commit: String,
|
||||
|
||||
/// The github account used to clone (used to access private repos).
|
||||
/// Empty string is public clone (only public repos).
|
||||
#[serde(default)]
|
||||
pub github_account: String,
|
||||
|
||||
/// The dockerhub account used to push the image to dockerhub.
|
||||
/// Empty string means no dockerhub push (server local build).
|
||||
#[serde(default)]
|
||||
pub docker_account: String,
|
||||
|
||||
/// The docker organization which the image should be pushed under.
|
||||
/// Empty string means no organization.
|
||||
#[serde(default)]
|
||||
pub docker_organization: String,
|
||||
|
||||
/// The optional command run after repo clone and before docker build.
|
||||
#[serde(default)]
|
||||
pub pre_build: SystemCommand,
|
||||
|
||||
/// The path of the docker build context relative to the root of the repo.
|
||||
/// Default: "." (the root of the repo).
|
||||
#[serde(default = "default_build_path")]
|
||||
pub build_path: String,
|
||||
|
||||
/// The path of the dockerfile relative to the build path.
|
||||
#[serde(default = "default_dockerfile_path")]
|
||||
pub dockerfile_path: String,
|
||||
|
||||
/// Docker build arguments
|
||||
#[serde(default)]
|
||||
pub build_args: Vec<EnvironmentVar>,
|
||||
|
||||
/// Docker labels
|
||||
#[serde(default)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
|
||||
/// Any extra docker cli arguments to be included in the build command
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// Whether to use buildx to build (eg `docker buildx build ...`)
|
||||
#[serde(default)]
|
||||
pub use_buildx: bool,
|
||||
|
||||
/// Whether incoming webhooks actually trigger action.
|
||||
#[serde(default = "default_webhook_enabled")]
|
||||
pub webhook_enabled: bool,
|
||||
}
|
||||
|
||||
impl From<BuildConfig>
|
||||
for monitor_client::entities::build::BuildConfig
|
||||
{
|
||||
fn from(value: BuildConfig) -> Self {
|
||||
monitor_client::entities::build::BuildConfig {
|
||||
builder_id: value.builder_id,
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
version: monitor_client::entities::Version {
|
||||
major: value.version.major,
|
||||
minor: value.version.minor,
|
||||
patch: value.version.patch,
|
||||
},
|
||||
repo: value.repo,
|
||||
branch: value.branch,
|
||||
commit: value.commit,
|
||||
github_account: value.github_account,
|
||||
pre_build: monitor_client::entities::SystemCommand {
|
||||
path: value.pre_build.path,
|
||||
command: value.pre_build.command,
|
||||
},
|
||||
build_path: value.build_path,
|
||||
dockerfile_path: value.dockerfile_path,
|
||||
build_args: value
|
||||
.build_args
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
labels: value.labels.into_iter().map(Into::into).collect(),
|
||||
extra_args: value.extra_args,
|
||||
use_buildx: value.use_buildx,
|
||||
image_registry: if value.docker_account.is_empty() {
|
||||
ImageRegistry::None(NoData {})
|
||||
} else {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account: value.docker_account,
|
||||
organization: value.docker_organization,
|
||||
})
|
||||
},
|
||||
webhook_enabled: value.webhook_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_branch() -> String {
|
||||
String::from("main")
|
||||
}
|
||||
|
||||
fn default_build_path() -> String {
|
||||
String::from(".")
|
||||
}
|
||||
|
||||
fn default_dockerfile_path() -> String {
|
||||
String::from("Dockerfile")
|
||||
}
|
||||
|
||||
fn default_webhook_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Default for BuildConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
builder_id: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
version: Default::default(),
|
||||
repo: Default::default(),
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
docker_account: Default::default(),
|
||||
docker_organization: Default::default(),
|
||||
pre_build: Default::default(),
|
||||
build_path: default_build_path(),
|
||||
dockerfile_path: default_dockerfile_path(),
|
||||
build_args: Default::default(),
|
||||
labels: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
use_buildx: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
deployment::{
|
||||
Conversion, DeploymentImage, RestartMode, TerminationSignal,
|
||||
TerminationSignalLabel,
|
||||
},
|
||||
NoData,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{resource::Resource, EnvironmentVar};
|
||||
|
||||
pub type Deployment = Resource<DeploymentConfig, ()>;
|
||||
|
||||
impl From<Deployment>
|
||||
for monitor_client::entities::deployment::Deployment
|
||||
{
|
||||
fn from(value: Deployment) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
updated_at: value.updated_at,
|
||||
tags: value.tags,
|
||||
info: value.info,
|
||||
config: value.config.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DeploymentConfig {
|
||||
/// The id of server the deployment is deployed on.
|
||||
#[serde(default, alias = "server")]
|
||||
pub server_id: String,
|
||||
|
||||
/// Whether to send ContainerStateChange alerts for this deployment.
|
||||
#[serde(default = "default_send_alerts")]
|
||||
pub send_alerts: bool,
|
||||
|
||||
/// The image which the deployment deploys.
|
||||
/// Can either be a user inputted image, or a Monitor build.
|
||||
#[serde(default)]
|
||||
pub image: DeploymentImage,
|
||||
|
||||
/// Override the account used to pull the image from the registry.
|
||||
/// If it is empty, will use the same account as the build.
|
||||
#[serde(default)]
|
||||
pub docker_account: String,
|
||||
|
||||
/// Whether to skip secret interpolation into the deployment environment variables.
|
||||
#[serde(default)]
|
||||
pub skip_secret_interp: bool,
|
||||
|
||||
/// Whether to redeploy the deployment whenever the attached build finishes.
|
||||
#[serde(default)]
|
||||
pub redeploy_on_build: bool,
|
||||
|
||||
/// Labels attached to various termination signal options.
|
||||
/// Used to specify different shutdown functionality depending on the termination signal.
|
||||
#[serde(default)]
|
||||
pub term_signal_labels: Vec<TerminationSignalLabel>,
|
||||
|
||||
/// The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal).
|
||||
#[serde(default)]
|
||||
pub termination_signal: TerminationSignal,
|
||||
|
||||
/// The termination timeout.
|
||||
#[serde(default = "default_termination_timeout")]
|
||||
pub termination_timeout: i32,
|
||||
|
||||
/// The container port mapping.
|
||||
/// Irrelevant if container network is `host`.
|
||||
/// Maps ports on host to ports on container.
|
||||
#[serde(default)]
|
||||
pub ports: Vec<Conversion>,
|
||||
|
||||
/// The container volume mapping.
|
||||
/// Maps files / folders on host to files / folders in container.
|
||||
#[serde(default)]
|
||||
pub volumes: Vec<Conversion>,
|
||||
|
||||
/// The environment variables passed to the container.
|
||||
#[serde(default)]
|
||||
pub environment: Vec<EnvironmentVar>,
|
||||
|
||||
/// The docker labels given to the container.
|
||||
#[serde(default)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
|
||||
/// The network attached to the container.
|
||||
/// Default is `host`.
|
||||
#[serde(default = "default_network")]
|
||||
pub network: String,
|
||||
|
||||
/// The restart mode given to the container.
|
||||
#[serde(default)]
|
||||
pub restart: RestartMode,
|
||||
|
||||
/// This is interpolated at the end of the `docker run` command,
|
||||
/// which means they are either passed to the containers inner process,
|
||||
/// or replaces the container command, depending on use of ENTRYPOINT or CMD in dockerfile.
|
||||
/// Empty is no command.
|
||||
#[serde(default)]
|
||||
pub command: String,
|
||||
|
||||
/// Extra args which are interpolated into the `docker run` command,
|
||||
/// and affect the container configuration.
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<DeploymentConfig>
|
||||
for monitor_client::entities::deployment::DeploymentConfig
|
||||
{
|
||||
fn from(value: DeploymentConfig) -> Self {
|
||||
Self {
|
||||
server_id: value.server_id,
|
||||
send_alerts: value.send_alerts,
|
||||
image: value.image,
|
||||
image_registry: if value.docker_account.is_empty() {
|
||||
ImageRegistry::None(NoData {})
|
||||
} else {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account: value.docker_account,
|
||||
..Default::default()
|
||||
})
|
||||
},
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
redeploy_on_build: value.redeploy_on_build,
|
||||
term_signal_labels: value.term_signal_labels,
|
||||
termination_signal: value.termination_signal,
|
||||
termination_timeout: value.termination_timeout,
|
||||
ports: value.ports,
|
||||
volumes: value.volumes,
|
||||
environment: value
|
||||
.environment
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
labels: value.labels.into_iter().map(Into::into).collect(),
|
||||
network: value.network,
|
||||
restart: value.restart,
|
||||
command: value.command,
|
||||
extra_args: value.extra_args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_send_alerts() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_termination_timeout() -> i32 {
|
||||
10
|
||||
}
|
||||
|
||||
fn default_network() -> String {
|
||||
String::from("host")
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use mungos::{init::MongoBuilder, mongodb::Collection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod build;
|
||||
pub mod deployment;
|
||||
pub mod resource;
|
||||
|
||||
pub struct DbClient {
|
||||
pub deployments: Collection<deployment::Deployment>,
|
||||
pub builds: Collection<build::Build>,
|
||||
}
|
||||
|
||||
impl DbClient {
|
||||
pub async fn new(
|
||||
legacy_uri: &str,
|
||||
legacy_db_name: &str,
|
||||
) -> DbClient {
|
||||
let client = MongoBuilder::default()
|
||||
.uri(legacy_uri)
|
||||
.build()
|
||||
.await
|
||||
.expect("failed to init legacy mongo client");
|
||||
let db = client.database(legacy_db_name);
|
||||
DbClient {
|
||||
deployments: db.collection("Deployment"),
|
||||
builds: db.collection("Build"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
)]
|
||||
pub struct Version {
|
||||
pub major: i32,
|
||||
pub minor: i32,
|
||||
pub patch: i32,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq,
|
||||
)]
|
||||
pub struct SystemCommand {
|
||||
#[serde(default)]
|
||||
pub path: String,
|
||||
#[serde(default)]
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
)]
|
||||
pub struct EnvironmentVar {
|
||||
pub variable: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<EnvironmentVar>
|
||||
for monitor_client::entities::EnvironmentVar
|
||||
{
|
||||
fn from(value: EnvironmentVar) -> Self {
|
||||
monitor_client::entities::EnvironmentVar {
|
||||
variable: value.variable,
|
||||
value: value.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Resource<Config, Info: Default = ()> {
|
||||
/// The Mongo ID of the resource.
|
||||
/// This field is de/serialized from/to JSON as
|
||||
/// `{ "_id": { "$oid": "..." }, ...(rest of serialized Resource<T>) }`
|
||||
#[serde(
|
||||
default,
|
||||
rename = "_id",
|
||||
skip_serializing_if = "String::is_empty",
|
||||
with = "hex_string_as_object_id"
|
||||
)]
|
||||
pub id: String,
|
||||
|
||||
/// The resource name.
|
||||
/// This is guaranteed unique among others of the same resource type.
|
||||
pub name: String,
|
||||
|
||||
/// A description for the resource
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
|
||||
/// When description last updated
|
||||
#[serde(default)]
|
||||
pub updated_at: i64,
|
||||
|
||||
/// Tag Ids
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// Resource-specific information (not user configurable).
|
||||
#[serde(default)]
|
||||
pub info: Info,
|
||||
|
||||
/// Resource-specific configuration.
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ResourceListItem<Info> {
|
||||
/// The resource id
|
||||
pub id: String,
|
||||
/// The resource type, ie `Server` or `Deployment`
|
||||
// #[serde(rename = "type")]
|
||||
// pub resource_type: ResourceTargetVariant,
|
||||
/// The resource name
|
||||
pub name: String,
|
||||
/// Tag Ids
|
||||
pub tags: Vec<String>,
|
||||
/// Resource specific info
|
||||
pub info: Info,
|
||||
}
|
||||
@@ -11,8 +11,15 @@ use serde::Deserialize;
|
||||
mod legacy;
|
||||
mod migrate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum AppMode {
|
||||
V0,
|
||||
V1_6,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
app_mode: AppMode,
|
||||
legacy_uri: String,
|
||||
legacy_db_name: String,
|
||||
target_uri: String,
|
||||
@@ -28,13 +35,26 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let env: Env = envy::from_env()?;
|
||||
|
||||
let legacy_db =
|
||||
legacy::v0::DbClient::new(&env.legacy_uri, &env.legacy_db_name)
|
||||
match env.app_mode {
|
||||
AppMode::V0 => {
|
||||
let legacy_db = legacy::v0::DbClient::new(
|
||||
&env.legacy_uri,
|
||||
&env.legacy_db_name,
|
||||
)
|
||||
.await;
|
||||
let target_db =
|
||||
DbClient::new(&env.target_uri, &env.target_db_name).await?;
|
||||
|
||||
migrate::migrate_all(&legacy_db, &target_db).await?;
|
||||
let target_db =
|
||||
DbClient::new(&env.target_uri, &env.target_db_name).await?;
|
||||
migrate::v0::migrate_all(&legacy_db, &target_db).await?
|
||||
}
|
||||
AppMode::V1_6 => {
|
||||
let db = legacy::v1_6::DbClient::new(
|
||||
&env.target_uri,
|
||||
&env.target_db_name,
|
||||
)
|
||||
.await;
|
||||
migrate::v1_6::migrate_all_in_place(&db).await?
|
||||
}
|
||||
}
|
||||
|
||||
info!("finished!");
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod v0;
|
||||
pub mod v1_6;
|
||||
@@ -0,0 +1,74 @@
|
||||
use anyhow::Context;
|
||||
use monitor_client::entities::{
|
||||
build::Build, deployment::Deployment,
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::bson::{doc, to_document},
|
||||
};
|
||||
|
||||
use crate::legacy::v1_6;
|
||||
|
||||
pub async fn migrate_all_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_deployments_in_place(db).await?;
|
||||
migrate_builds_in_place(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_deployments_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let deployments = find_collect(&db.deployments, None, None)
|
||||
.await
|
||||
.context("failed to get deployments")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<Deployment>>();
|
||||
|
||||
info!("migrating {} deployments...", deployments.len());
|
||||
|
||||
for deployment in deployments {
|
||||
db.deployments
|
||||
.update_one(
|
||||
doc! { "name": &deployment.name },
|
||||
doc! { "$set": to_document(&deployment)? },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to insert deployments on target")?;
|
||||
}
|
||||
|
||||
info!("deployments have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_builds_in_place(
|
||||
db: &v1_6::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let builds = find_collect(&db.builds, None, None)
|
||||
.await
|
||||
.context("failed to get builds")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<Build>>();
|
||||
|
||||
info!("migrating {} builds...", builds.len());
|
||||
|
||||
for build in builds {
|
||||
db.builds
|
||||
.update_one(
|
||||
doc! { "name": &build.name },
|
||||
doc! { "$set": to_document(&build)? },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to insert builds on target")?;
|
||||
}
|
||||
|
||||
info!("builds have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,7 +17,9 @@ path = "src/main.rs"
|
||||
# local
|
||||
monitor_client = { workspace = true, features = ["docker"] }
|
||||
periphery_client.workspace = true
|
||||
command.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
async_timing_util.workspace = true
|
||||
|
||||
@@ -7,7 +7,7 @@ use periphery_client::api::build::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::docker::{self, client::docker_client},
|
||||
docker::{self, client::docker_client},
|
||||
State,
|
||||
};
|
||||
|
||||
@@ -17,12 +17,12 @@ impl Resolve<Build> for State {
|
||||
&self,
|
||||
Build {
|
||||
build,
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers,
|
||||
}: Build,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
docker::build::build(&build, docker_token, replacers).await
|
||||
docker::build::build(&build, registry_token, replacers).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use periphery_client::api::container::*;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::docker::{self, client::docker_client},
|
||||
docker::{self, client::docker_client},
|
||||
State,
|
||||
};
|
||||
|
||||
@@ -192,9 +192,9 @@ impl Resolve<Deploy> for State {
|
||||
&self,
|
||||
Deploy {
|
||||
deployment,
|
||||
docker_token,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
registry_token,
|
||||
replacers,
|
||||
}: Deploy,
|
||||
_: (),
|
||||
@@ -207,7 +207,7 @@ impl Resolve<Deploy> for State {
|
||||
stop_time
|
||||
.unwrap_or(deployment.config.termination_timeout)
|
||||
.into(),
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
use anyhow::anyhow;
|
||||
use monitor_client::entities::{to_monitor_name, update::Log};
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
to_monitor_name, update::Log, CloneArgs, LatestCommit,
|
||||
};
|
||||
use periphery_client::api::git::{
|
||||
CloneRepo, DeleteRepo, GetLatestCommit, GetLatestCommitResponse,
|
||||
PullRepo,
|
||||
CloneRepo, DeleteRepo, GetLatestCommit, PullRepo,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{config::periphery_config, helpers::git, State};
|
||||
use crate::{
|
||||
config::periphery_config, helpers::get_github_token, State,
|
||||
};
|
||||
|
||||
impl Resolve<GetLatestCommit, ()> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetLatestCommit { name }: GetLatestCommit,
|
||||
_: (),
|
||||
) -> anyhow::Result<GetLatestCommitResponse> {
|
||||
) -> anyhow::Result<LatestCommit> {
|
||||
let repo_path = periphery_config().repo_dir.join(name);
|
||||
if !repo_path.is_dir() {
|
||||
return Err(anyhow!(
|
||||
@@ -31,7 +34,19 @@ impl Resolve<CloneRepo> for State {
|
||||
CloneRepo { args, github_token }: CloneRepo,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
git::clone(args, github_token).await
|
||||
let CloneArgs { github_account, .. } = &args;
|
||||
let github_token = match (github_account, github_token) {
|
||||
(None, _) => None,
|
||||
(Some(_), Some(token)) => Some(token),
|
||||
(Some(account), None) => Some(
|
||||
get_github_token(account)
|
||||
.context(
|
||||
"failed to get github token from periphery config",
|
||||
)?
|
||||
.clone(),
|
||||
),
|
||||
};
|
||||
git::clone(args, &periphery_config().repo_dir, github_token).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{update::Log, SystemCommand};
|
||||
use periphery_client::api::{
|
||||
build::*, container::*, git::*, network::*, stats::*, GetAccounts,
|
||||
@@ -10,8 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{accounts_response, secrets_response},
|
||||
helpers::{docker, run_monitor_command},
|
||||
State,
|
||||
docker, State,
|
||||
};
|
||||
|
||||
mod build;
|
||||
|
||||
@@ -7,7 +7,7 @@ use periphery_client::api::network::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::docker::{self, client::docker_client},
|
||||
docker::{self, client::docker_client},
|
||||
State,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
build::{Build, BuildConfig},
|
||||
optional_string, to_monitor_name,
|
||||
get_image_name, optional_string, to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
helpers::{get_docker_token, run_monitor_command},
|
||||
};
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
@@ -20,15 +18,17 @@ pub async fn prune_images() -> Log {
|
||||
run_monitor_command("prune images", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(docker_token, core_replacers))]
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn build(
|
||||
Build {
|
||||
build: &Build,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
let Build {
|
||||
name,
|
||||
config:
|
||||
BuildConfig {
|
||||
version,
|
||||
docker_account,
|
||||
docker_organization,
|
||||
skip_secret_interp,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
@@ -36,61 +36,64 @@ pub async fn build(
|
||||
labels,
|
||||
extra_args,
|
||||
use_buildx,
|
||||
image_registry,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &Build,
|
||||
docker_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
} = build;
|
||||
|
||||
let mut logs = Vec::new();
|
||||
let docker_token = match (
|
||||
docker_token,
|
||||
get_docker_token(&optional_string(docker_account)),
|
||||
) {
|
||||
(Some(docker_token), _) => Some(docker_token),
|
||||
(None, Ok(docker_token)) => docker_token,
|
||||
(None, Err(e)) => {
|
||||
logs.push(Log::error("build", serialize_error_pretty(&e)));
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
let name = to_monitor_name(name);
|
||||
let using_account =
|
||||
docker_login(&optional_string(docker_account), &docker_token)
|
||||
|
||||
// Maybe docker login
|
||||
let should_push =
|
||||
match docker_login(image_registry, registry_token.as_deref())
|
||||
.await
|
||||
.context("failed to login to docker")?;
|
||||
{
|
||||
Ok(should_push) => should_push,
|
||||
Err(e) => {
|
||||
logs.push(Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
));
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
|
||||
// Get paths
|
||||
let name = to_monitor_name(name);
|
||||
let build_dir =
|
||||
periphery_config().repo_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = match optional_string(dockerfile_path) {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
|
||||
// Get command parts
|
||||
let image_name = get_image_name(build);
|
||||
let build_args = parse_build_args(build_args);
|
||||
let labels = parse_labels(labels);
|
||||
let extra_args = parse_extra_args(extra_args);
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
let image_name = get_image_name(
|
||||
&name,
|
||||
&optional_string(docker_account),
|
||||
&optional_string(docker_organization),
|
||||
);
|
||||
let image_tags = image_tags(&image_name, version);
|
||||
let docker_push = if using_account {
|
||||
format!(" && docker image push --all-tags {image_name}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let push_command = should_push
|
||||
.then(|| format!(" && docker image push --all-tags {image_name}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{docker_push}",
|
||||
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
|
||||
build_dir.display()
|
||||
);
|
||||
|
||||
if *skip_secret_interp {
|
||||
let build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
info!("finished building docker image");
|
||||
logs.push(build_log);
|
||||
} else {
|
||||
// Interpolate any missing secrets
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
@@ -112,23 +115,8 @@ pub async fn build(
|
||||
svi::replace_in_string(&build_log.stderr, &replacers);
|
||||
logs.push(build_log);
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
fn get_image_name(
|
||||
name: &str,
|
||||
docker_account: &Option<String>,
|
||||
docker_organization: &Option<String>,
|
||||
) -> String {
|
||||
match docker_organization {
|
||||
Some(docker_org) => format!("{docker_org}/{name}"),
|
||||
None => match docker_account {
|
||||
Some(docker_account) => {
|
||||
format!("{docker_account}/{name}")
|
||||
}
|
||||
None => name.to_string(),
|
||||
},
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
fn image_tags(image_name: &str, version: &Version) -> String {
|
||||
+14
-27
@@ -1,25 +1,20 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
deployment::{
|
||||
Conversion, Deployment, DeploymentConfig, DeploymentImage,
|
||||
DockerContainerStats, RestartMode, TerminationSignal,
|
||||
},
|
||||
optional_string, to_monitor_name,
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, SearchCombinator,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
helpers::{
|
||||
docker::{parse_extra_args, parse_labels},
|
||||
get_docker_token, run_monitor_command,
|
||||
},
|
||||
};
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::docker_login;
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn container_log(container_name: &str, tail: u64) -> Log {
|
||||
@@ -178,34 +173,26 @@ async fn pull_image(image: &str) -> Log {
|
||||
run_monitor_command("docker pull", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(docker_token, core_replacers))]
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn deploy(
|
||||
deployment: &Deployment,
|
||||
stop_signal: Option<TerminationSignal>,
|
||||
stop_time: Option<i32>,
|
||||
docker_token: Option<String>,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> Log {
|
||||
let docker_token = match (
|
||||
docker_token,
|
||||
get_docker_token(&optional_string(
|
||||
&deployment.config.docker_account,
|
||||
)),
|
||||
) {
|
||||
(Some(token), _) => Some(token),
|
||||
(None, Ok(token)) => token,
|
||||
(None, Err(e)) => {
|
||||
return Log::error("docker login", serialize_error_pretty(&e))
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = docker_login(
|
||||
&optional_string(&deployment.config.docker_account),
|
||||
&docker_token,
|
||||
&deployment.config.image_registry,
|
||||
registry_token.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Log::error("docker login", serialize_error_pretty(&e));
|
||||
return Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let image = if let DeploymentImage::Image { image } =
|
||||
@@ -0,0 +1,84 @@
|
||||
use anyhow::anyhow;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
update::Log,
|
||||
EnvironmentVar,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::helpers::{get_docker_token, get_github_token};
|
||||
|
||||
pub mod build;
|
||||
pub mod client;
|
||||
pub mod container;
|
||||
pub mod network;
|
||||
|
||||
/// Returns whether build result should be pushed after build
|
||||
pub async fn docker_login(
|
||||
registry: &ImageRegistry,
|
||||
// For local token override from core.
|
||||
registry_token: Option<&str>,
|
||||
) -> anyhow::Result<bool> {
|
||||
match registry {
|
||||
ImageRegistry::None(_) => Ok(false),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for DockerHub registry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_docker_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::Ghcr(CloudRegistryConfig { account, .. }) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_github_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login ghcr.io -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_extra_args(extra_args: &[String]) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if !args.is_empty() {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_labels(labels: &[EnvironmentVar]) -> String {
|
||||
labels
|
||||
.iter()
|
||||
.map(|p| format!(" --label {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_system() -> Log {
|
||||
let command = String::from("docker system prune -a -f");
|
||||
run_monitor_command("prune system", command).await
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::update::Log;
|
||||
|
||||
use crate::helpers::run_monitor_command;
|
||||
|
||||
#[instrument]
|
||||
pub async fn create_network(
|
||||
name: &str,
|
||||
@@ -29,7 +29,6 @@ async fn task(
|
||||
req_id: Uuid,
|
||||
request: crate::api::PeripheryRequest,
|
||||
) -> anyhow::Result<String> {
|
||||
info!("request {req_id} | {request:?}");
|
||||
let timer = Instant::now();
|
||||
|
||||
let res =
|
||||
@@ -48,7 +47,7 @@ async fn task(
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
pub fn get_github_token(
|
||||
github_account: &String,
|
||||
) -> anyhow::Result<&'static String> {
|
||||
periphery_config()
|
||||
.github_accounts
|
||||
.get(github_account)
|
||||
.with_context(|| format!("did not find token in config for github account {github_account}"))
|
||||
}
|
||||
|
||||
pub fn get_docker_token(
|
||||
docker_account: &String,
|
||||
) -> anyhow::Result<&'static String> {
|
||||
periphery_config()
|
||||
.docker_accounts
|
||||
.get(docker_account)
|
||||
.with_context(|| format!("did not find token in config for docker account {docker_account}"))
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use monitor_client::entities::{update::Log, EnvironmentVar};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use super::run_monitor_command;
|
||||
|
||||
pub mod build;
|
||||
pub mod client;
|
||||
pub mod container;
|
||||
pub mod network;
|
||||
|
||||
pub fn get_docker_username_pw(
|
||||
docker_account: &Option<String>,
|
||||
docker_token: &Option<String>,
|
||||
) -> anyhow::Result<Option<(String, String)>> {
|
||||
match docker_account {
|
||||
Some(docker_account) => match docker_token {
|
||||
Some(docker_token) => Ok(Some((docker_account.to_owned(), docker_token.to_owned()))),
|
||||
None => Err(anyhow!(
|
||||
"docker token for account {docker_account} has not been configured on this client"
|
||||
)),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn docker_login(
|
||||
docker_account: &Option<String>,
|
||||
docker_token: &Option<String>,
|
||||
) -> anyhow::Result<bool> {
|
||||
let docker_account_u_pw =
|
||||
get_docker_username_pw(docker_account, docker_token)?;
|
||||
if let Some((username, password)) = &docker_account_u_pw {
|
||||
let login = format!("docker login -u {username} -p {password}");
|
||||
async_run_command(&login).await;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_extra_args(extra_args: &[String]) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if !args.is_empty() {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_labels(labels: &[EnvironmentVar]) -> String {
|
||||
labels
|
||||
.iter()
|
||||
.map(|p| format!(" --label {}=\"{}\"", p.variable, p.value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn prune_system() -> Log {
|
||||
let command = String::from("docker system prune -a -f");
|
||||
run_monitor_command("prune system", command).await
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use async_timing_util::unix_timestamp_ms;
|
||||
use monitor_client::entities::update::Log;
|
||||
use run_command::{async_run_command, CommandOutput};
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
pub mod docker;
|
||||
pub mod git;
|
||||
|
||||
pub fn get_github_token(
|
||||
github_account: &Option<String>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match github_account {
|
||||
Some(account) => {
|
||||
match periphery_config().github_accounts.get(account) {
|
||||
Some(token) => Ok(Some(token.to_owned())),
|
||||
None => Err(anyhow!(
|
||||
"did not find token in config for github account {account}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_docker_token(
|
||||
docker_account: &Option<String>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match docker_account {
|
||||
Some(account) => {
|
||||
match periphery_config().docker_accounts.get(account) {
|
||||
Some(token) => Ok(Some(token.to_owned())),
|
||||
None => Err(anyhow!(
|
||||
"did not find token in config for docker account {account}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_monitor_command(
|
||||
stage: &str,
|
||||
command: String,
|
||||
) -> Log {
|
||||
let start_ts = unix_timestamp_ms() as i64;
|
||||
let output = async_run_command(&command).await;
|
||||
output_into_log(stage, command, start_ts, output)
|
||||
}
|
||||
|
||||
pub fn output_into_log(
|
||||
stage: &str,
|
||||
command: String,
|
||||
start_ts: i64,
|
||||
output: CommandOutput,
|
||||
) -> Log {
|
||||
let success = output.success();
|
||||
Log {
|
||||
stage: stage.to_string(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
command,
|
||||
success,
|
||||
start_ts,
|
||||
end_ts: unix_timestamp_ms() as i64,
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use termination_signal::tokio::immediate_term_handle;
|
||||
|
||||
mod api;
|
||||
mod config;
|
||||
mod docker;
|
||||
mod guard;
|
||||
mod handler;
|
||||
mod helpers;
|
||||
|
||||
+15
-15
@@ -17,29 +17,29 @@ docker = ["dep:bollard"]
|
||||
[dependencies]
|
||||
# mogh
|
||||
mongo_indexed = { workspace = true, optional = true }
|
||||
serror.workspace = true
|
||||
resolver_api.workspace = true
|
||||
derive_default_builder.workspace = true
|
||||
derive_empty_traits.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
partial_derive2.workspace = true
|
||||
derive_variants.workspace = true
|
||||
derive_default_builder.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
resolver_api.workspace = true
|
||||
serror.workspace = true
|
||||
# external
|
||||
bollard = { workspace = true, optional = true }
|
||||
reqwest.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
envy.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio-tungstenite.workspace = true
|
||||
futures.workspace = true
|
||||
typeshare.workspace = true
|
||||
strum.workspace = true
|
||||
derive_builder.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio-util.workspace = true
|
||||
thiserror.workspace = true
|
||||
typeshare.workspace = true
|
||||
futures.workspace = true
|
||||
reqwest.workspace = true
|
||||
tracing.workspace = true
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
tokio.workspace = true
|
||||
strum.workspace = true
|
||||
envy.workspace = true
|
||||
uuid.workspace = true
|
||||
clap.workspace = true
|
||||
bson.workspace = true
|
||||
@@ -1,4 +1,4 @@
|
||||
use clap::Subcommand;
|
||||
use clap::{Parser, Subcommand};
|
||||
use derive_variants::EnumVariants;
|
||||
use resolver_api::HasResponse;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -11,6 +11,7 @@ mod procedure;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod sync;
|
||||
|
||||
pub use build::*;
|
||||
pub use deployment::*;
|
||||
@@ -18,8 +19,9 @@ pub use procedure::*;
|
||||
pub use repo::*;
|
||||
pub use server::*;
|
||||
pub use server_template::*;
|
||||
pub use sync::*;
|
||||
|
||||
use crate::entities::NoData;
|
||||
use crate::entities::{NoData, I64};
|
||||
|
||||
pub trait MonitorExecuteRequest: HasResponse {}
|
||||
|
||||
@@ -69,4 +71,17 @@ pub enum Execution {
|
||||
PruneNetworks(PruneNetworks),
|
||||
PruneImages(PruneImages),
|
||||
PruneContainers(PruneContainers),
|
||||
|
||||
// SYNC
|
||||
RunSync(RunSync),
|
||||
|
||||
// SLEEP
|
||||
Sleep(Sleep),
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Parser)]
|
||||
pub struct Sleep {
|
||||
#[serde(default)]
|
||||
pub duration_ms: I64,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user