mirror of
https://github.com/moghtech/komodo.git
synced 2026-11-02 08:29:18 -06:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99cf87da0 | ||
|
|
8e19eb7b0f | ||
|
|
78a0b56c73 | ||
|
|
bf5dc52237 | ||
|
|
482ea59d4c | ||
|
|
7740d36f49 | ||
|
|
820754deda | ||
|
|
4219884198 | ||
|
|
d9e24cc35a | ||
|
|
8d2ce884d9 | ||
|
|
313b000e64 | ||
|
|
c2f9e29605 | ||
|
|
8c6f38cafb | ||
|
|
4a03eba99a | ||
|
|
79fe078e3b | ||
|
|
6be032fcd4 | ||
|
|
d0c94278ec | ||
|
|
03ae7268fd | ||
|
|
f443294818 | ||
|
|
2202835d86 | ||
|
|
98fbc7a506 | ||
|
|
8ee89296e1 | ||
|
|
989c3d2d01 | ||
|
|
dc72883b90 | ||
|
|
e99364430f | ||
|
|
e106e38cd9 | ||
|
|
e4d0c56e49 | ||
|
|
7427a158f4 | ||
|
|
b926f89954 | ||
|
|
e666a22f08 | ||
|
|
4107f779a5 | ||
|
|
828d6cdfed | ||
|
|
fe82400a99 | ||
|
|
e37fc6adde | ||
|
|
c21c8f99ae | ||
|
|
78a63f92bb | ||
|
|
ce67655021 | ||
|
|
2ccecf38f2 | ||
|
|
1ddae31aad | ||
|
|
097fbefa63 | ||
|
|
b51442a661 | ||
|
|
a21d49d224 | ||
|
|
c99a33880e | ||
|
|
6ee55262ba | ||
|
|
878b9b55bb | ||
|
|
af6193f83a | ||
|
|
b8fefddd8b | ||
|
|
7f490f5bf2 | ||
|
|
efa7c13286 | ||
|
|
f913be7a0b | ||
|
|
35901ef7ea | ||
|
|
5b938490fc | ||
|
|
a7326a0116 | ||
|
|
877bda91d7 | ||
|
|
439a091e50 | ||
|
|
b0e89f4963 | ||
|
|
b1e4b55ba1 | ||
|
|
d4a1891c70 | ||
|
|
9db7592d7e |
Generated
+506
-126
File diff suppressed because it is too large
Load Diff
+20
-16
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.10.0"
|
||||
version = "1.12.0"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,7 +15,7 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.10.0"
|
||||
monitor_client = "1.12.0"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
command = { path = "lib/command" }
|
||||
@@ -24,12 +24,12 @@ git = { path = "lib/git" }
|
||||
|
||||
# MOGH
|
||||
run_command = { version = "0.0.6", features = ["async_tokio"] }
|
||||
serror = { version = "0.4.3", default-features = false }
|
||||
serror = { version = "0.4.6", 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"
|
||||
async_timing_util = "0.1.14"
|
||||
async_timing_util = "1.0.0"
|
||||
partial_derive2 = "0.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "1.0.0"
|
||||
@@ -40,7 +40,7 @@ mungos = "1.0.0"
|
||||
svi = "1.0.1"
|
||||
|
||||
# ASYNC
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
tokio = { version = "1.38.1", features = ["full"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
tokio-util = "0.7.11"
|
||||
futures = "0.3.30"
|
||||
@@ -55,14 +55,14 @@ tokio-tungstenite = "0.23.1"
|
||||
|
||||
# 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.118"
|
||||
toml = "0.8.14"
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
serde_json = "1.0.120"
|
||||
toml = "0.8.15"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.86"
|
||||
thiserror = "1.0.61"
|
||||
thiserror = "1.0.63"
|
||||
|
||||
# LOGGING
|
||||
opentelemetry_sdk = { version = "0.23.0", features = ["rt-tokio"] }
|
||||
@@ -73,13 +73,14 @@ opentelemetry = "0.23.0"
|
||||
tracing = "0.1.40"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.7", features = ["derive"] }
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
dotenv = "0.15.0"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO
|
||||
uuid = { version = "1.9.1", features = ["v4", "fast-rng", "serde"] }
|
||||
uuid = { version = "1.10.0", features = ["v4", "fast-rng", "serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
nom_pem = "4.0.0"
|
||||
bcrypt = "0.15.1"
|
||||
base64 = "0.22.1"
|
||||
hmac = "0.12.1"
|
||||
@@ -90,15 +91,18 @@ hex = "0.4.3"
|
||||
|
||||
# SYSTEM
|
||||
bollard = "0.16.1"
|
||||
sysinfo = "0.30.12"
|
||||
sysinfo = "0.30.13"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.5.3"
|
||||
aws-sdk-ec2 = "1.53.0"
|
||||
aws-sdk-ecr = "1.33.0"
|
||||
aws-config = "1.5.4"
|
||||
aws-sdk-ec2 = "1.60.0"
|
||||
aws-sdk-ecr = "1.35.0"
|
||||
|
||||
# MISC
|
||||
derive_builder = "0.20.0"
|
||||
typeshare = "1.0.3"
|
||||
octorust = "0.7.0"
|
||||
colored = "2.1.0"
|
||||
regex = "1.10.5"
|
||||
bson = "2.11.0"
|
||||
|
||||
|
||||
+22
-23
@@ -20,7 +20,7 @@ pub fn name_to_build() -> &'static HashMap<String, Build> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullBuilds::default()),
|
||||
)
|
||||
.expect("failed to get builds from monitor")
|
||||
.expect("failed to get builds")
|
||||
.into_iter()
|
||||
.map(|build| (build.name.clone(), build))
|
||||
.collect()
|
||||
@@ -34,7 +34,7 @@ pub fn id_to_build() -> &'static HashMap<String, Build> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullBuilds::default()),
|
||||
)
|
||||
.expect("failed to get builds from monitor")
|
||||
.expect("failed to get builds")
|
||||
.into_iter()
|
||||
.map(|build| (build.id.clone(), build))
|
||||
.collect()
|
||||
@@ -48,7 +48,7 @@ pub fn name_to_deployment() -> &'static HashMap<String, Deployment> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullDeployments::default()),
|
||||
)
|
||||
.expect("failed to get deployments from monitor")
|
||||
.expect("failed to get deployments")
|
||||
.into_iter()
|
||||
.map(|deployment| (deployment.name.clone(), deployment))
|
||||
.collect()
|
||||
@@ -62,7 +62,7 @@ pub fn id_to_deployment() -> &'static HashMap<String, Deployment> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullDeployments::default()),
|
||||
)
|
||||
.expect("failed to get deployments from monitor")
|
||||
.expect("failed to get deployments")
|
||||
.into_iter()
|
||||
.map(|deployment| (deployment.id.clone(), deployment))
|
||||
.collect()
|
||||
@@ -76,7 +76,7 @@ pub fn name_to_server() -> &'static HashMap<String, Server> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullServers::default()),
|
||||
)
|
||||
.expect("failed to get servers from monitor")
|
||||
.expect("failed to get servers")
|
||||
.into_iter()
|
||||
.map(|server| (server.name.clone(), server))
|
||||
.collect()
|
||||
@@ -90,7 +90,7 @@ pub fn id_to_server() -> &'static HashMap<String, Server> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullServers::default()),
|
||||
)
|
||||
.expect("failed to get servers from monitor")
|
||||
.expect("failed to get servers")
|
||||
.into_iter()
|
||||
.map(|server| (server.id.clone(), server))
|
||||
.collect()
|
||||
@@ -104,7 +104,7 @@ pub fn name_to_builder() -> &'static HashMap<String, Builder> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullBuilders::default()),
|
||||
)
|
||||
.expect("failed to get builders from monitor")
|
||||
.expect("failed to get builders")
|
||||
.into_iter()
|
||||
.map(|builder| (builder.name.clone(), builder))
|
||||
.collect()
|
||||
@@ -118,7 +118,7 @@ pub fn id_to_builder() -> &'static HashMap<String, Builder> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullBuilders::default()),
|
||||
)
|
||||
.expect("failed to get builders from monitor")
|
||||
.expect("failed to get builders")
|
||||
.into_iter()
|
||||
.map(|builder| (builder.id.clone(), builder))
|
||||
.collect()
|
||||
@@ -132,7 +132,7 @@ pub fn name_to_alerter() -> &'static HashMap<String, Alerter> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullAlerters::default()),
|
||||
)
|
||||
.expect("failed to get alerters from monitor")
|
||||
.expect("failed to get alerters")
|
||||
.into_iter()
|
||||
.map(|alerter| (alerter.name.clone(), alerter))
|
||||
.collect()
|
||||
@@ -146,7 +146,7 @@ pub fn id_to_alerter() -> &'static HashMap<String, Alerter> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullAlerters::default()),
|
||||
)
|
||||
.expect("failed to get alerters from monitor")
|
||||
.expect("failed to get alerters")
|
||||
.into_iter()
|
||||
.map(|alerter| (alerter.id.clone(), alerter))
|
||||
.collect()
|
||||
@@ -160,7 +160,7 @@ pub fn name_to_repo() -> &'static HashMap<String, Repo> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullRepos::default()),
|
||||
)
|
||||
.expect("failed to get repos from monitor")
|
||||
.expect("failed to get repos")
|
||||
.into_iter()
|
||||
.map(|repo| (repo.name.clone(), repo))
|
||||
.collect()
|
||||
@@ -174,7 +174,7 @@ pub fn id_to_repo() -> &'static HashMap<String, Repo> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullRepos::default()),
|
||||
)
|
||||
.expect("failed to get repos from monitor")
|
||||
.expect("failed to get repos")
|
||||
.into_iter()
|
||||
.map(|repo| (repo.id.clone(), repo))
|
||||
.collect()
|
||||
@@ -188,7 +188,7 @@ pub fn name_to_procedure() -> &'static HashMap<String, Procedure> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullProcedures::default()),
|
||||
)
|
||||
.expect("failed to get procedures from monitor")
|
||||
.expect("failed to get procedures")
|
||||
.into_iter()
|
||||
.map(|procedure| (procedure.name.clone(), procedure))
|
||||
.collect()
|
||||
@@ -202,7 +202,7 @@ pub fn id_to_procedure() -> &'static HashMap<String, Procedure> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullProcedures::default()),
|
||||
)
|
||||
.expect("failed to get procedures from monitor")
|
||||
.expect("failed to get procedures")
|
||||
.into_iter()
|
||||
.map(|procedure| (procedure.id.clone(), procedure))
|
||||
.collect()
|
||||
@@ -218,7 +218,7 @@ pub fn name_to_server_template(
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullServerTemplates::default()),
|
||||
)
|
||||
.expect("failed to get server templates from monitor")
|
||||
.expect("failed to get server templates")
|
||||
.into_iter()
|
||||
.map(|procedure| (procedure.name.clone(), procedure))
|
||||
.collect()
|
||||
@@ -234,7 +234,7 @@ pub fn id_to_server_template(
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullServerTemplates::default()),
|
||||
)
|
||||
.expect("failed to get server templates from monitor")
|
||||
.expect("failed to get server templates")
|
||||
.into_iter()
|
||||
.map(|procedure| (procedure.id.clone(), procedure))
|
||||
.collect()
|
||||
@@ -249,7 +249,7 @@ pub fn name_to_resource_sync(
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullResourceSyncs::default()),
|
||||
)
|
||||
.expect("failed to get syncs from monitor")
|
||||
.expect("failed to get syncs")
|
||||
.into_iter()
|
||||
.map(|sync| (sync.name.clone(), sync))
|
||||
.collect()
|
||||
@@ -264,7 +264,7 @@ pub fn id_to_resource_sync() -> &'static HashMap<String, ResourceSync>
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListFullResourceSyncs::default()),
|
||||
)
|
||||
.expect("failed to get syncs from monitor")
|
||||
.expect("failed to get syncs")
|
||||
.into_iter()
|
||||
.map(|sync| (sync.id.clone(), sync))
|
||||
.collect()
|
||||
@@ -278,7 +278,7 @@ pub fn name_to_user_group() -> &'static HashMap<String, UserGroup> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListUserGroups::default()),
|
||||
)
|
||||
.expect("failed to get user groups from monitor")
|
||||
.expect("failed to get user groups")
|
||||
.into_iter()
|
||||
.map(|user_group| (user_group.name.clone(), user_group))
|
||||
.collect()
|
||||
@@ -292,8 +292,7 @@ pub fn name_to_variable() -> &'static HashMap<String, Variable> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListVariables::default()),
|
||||
)
|
||||
.expect("failed to get user groups from monitor")
|
||||
.variables
|
||||
.expect("failed to get variables")
|
||||
.into_iter()
|
||||
.map(|variable| (variable.name.clone(), variable))
|
||||
.collect()
|
||||
@@ -307,7 +306,7 @@ pub fn id_to_user() -> &'static HashMap<String, User> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListUsers::default()),
|
||||
)
|
||||
.expect("failed to get users from monitor")
|
||||
.expect("failed to get users")
|
||||
.into_iter()
|
||||
.map(|user| (user.id.clone(), user))
|
||||
.collect()
|
||||
@@ -320,7 +319,7 @@ pub fn id_to_tag() -> &'static HashMap<String, Tag> {
|
||||
futures::executor::block_on(
|
||||
monitor_client().read(read::ListTags::default()),
|
||||
)
|
||||
.expect("failed to get tags from monitor")
|
||||
.expect("failed to get tags")
|
||||
.into_iter()
|
||||
.map(|tag| (tag.id.clone(), tag))
|
||||
.collect()
|
||||
|
||||
@@ -45,9 +45,11 @@ axum-extra.workspace = true
|
||||
tower-http.workspace = true
|
||||
serde_json.workspace = true
|
||||
typeshare.workspace = true
|
||||
octorust.workspace = true
|
||||
tracing.workspace = true
|
||||
reqwest.workspace = true
|
||||
futures.workspace = true
|
||||
nom_pem.workspace = true
|
||||
anyhow.workspace = true
|
||||
dotenv.workspace = true
|
||||
bcrypt.workspace = true
|
||||
@@ -56,6 +58,7 @@ tokio.workspace = true
|
||||
tower.workspace = true
|
||||
serde.workspace = true
|
||||
strum.workspace = true
|
||||
regex.workspace = true
|
||||
axum.workspace = true
|
||||
toml.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -10,7 +10,7 @@ use monitor_client::{
|
||||
entities::{
|
||||
alert::{Alert, AlertData},
|
||||
all_logs_success,
|
||||
build::{Build, CloudRegistryConfig, ImageRegistry},
|
||||
build::{Build, ImageRegistry, StandardRegistryConfig},
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
config::core::{AwsEcrConfig, AwsEcrConfigWithCredentials},
|
||||
deployment::DeploymentState,
|
||||
@@ -79,9 +79,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (registry_token, aws_ecr) =
|
||||
validate_account_extract_registry_token_aws_ecr(&build).await?;
|
||||
|
||||
// get the action state for the build (or insert default).
|
||||
let action_state =
|
||||
action_states().build.get_or_insert_default(&build.id).await;
|
||||
@@ -91,6 +88,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.building = true)?;
|
||||
|
||||
let (registry_token, aws_ecr) =
|
||||
validate_account_extract_registry_token_aws_ecr(&build).await?;
|
||||
|
||||
build.config.version.increment();
|
||||
update.version = build.config.version;
|
||||
update_update(update.clone()).await?;
|
||||
@@ -156,17 +156,26 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
let variables = get_global_variables().await?;
|
||||
|
||||
// CLONE REPO
|
||||
|
||||
let github_token = core_config
|
||||
.github_accounts
|
||||
.get(&build.config.github_account)
|
||||
.cloned();
|
||||
let git_token = core_config
|
||||
.git_providers
|
||||
.iter()
|
||||
.find(|provider| provider.domain == build.config.git_provider)
|
||||
.and_then(|provider| {
|
||||
build.config.git_https = provider.https;
|
||||
provider
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| {
|
||||
account.username == build.config.git_account
|
||||
})
|
||||
.map(|account| account.token.clone())
|
||||
});
|
||||
|
||||
let res = tokio::select! {
|
||||
res = periphery
|
||||
.request(api::git::CloneRepo {
|
||||
args: (&build).into(),
|
||||
github_token,
|
||||
git_token,
|
||||
}) => res,
|
||||
_ = cancel.cancelled() => {
|
||||
info!("build cancelled during clone, cleaning up builder");
|
||||
@@ -354,12 +363,11 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
// don't hold response up for user
|
||||
tokio::spawn(async move {
|
||||
handle_post_build_redeploy(&build.id).await;
|
||||
info!("post build redeploy handled");
|
||||
});
|
||||
} else {
|
||||
warn!("build unsuccessful, alerting...");
|
||||
let target = update.target.clone();
|
||||
let version = update.version;
|
||||
let err = update.logs.iter().find(|l| !l.success).cloned();
|
||||
tokio::spawn(async move {
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
@@ -371,7 +379,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
data: AlertData::BuildFailed {
|
||||
id: build.id,
|
||||
name: build.name,
|
||||
err,
|
||||
version,
|
||||
},
|
||||
};
|
||||
@@ -407,9 +414,9 @@ async fn handle_early_return(
|
||||
}
|
||||
update_update(update.clone()).await?;
|
||||
if !update.success && !is_cancel {
|
||||
warn!("build unsuccessful, alerting...");
|
||||
let target = update.target.clone();
|
||||
let version = update.version;
|
||||
let err = update.logs.iter().find(|l| !l.success).cloned();
|
||||
tokio::spawn(async move {
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
@@ -422,7 +429,6 @@ async fn handle_early_return(
|
||||
id: build_id,
|
||||
name: build_name,
|
||||
version,
|
||||
err,
|
||||
},
|
||||
};
|
||||
send_alerts(&[alert]).await
|
||||
@@ -782,28 +788,15 @@ fn start_aws_builder_log(
|
||||
async fn validate_account_extract_registry_token_aws_ecr(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<(Option<String>, Option<AwsEcrConfig>)> {
|
||||
match &build.config.image_registry {
|
||||
ImageRegistry::None(_) => Ok((None, 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(), None))
|
||||
}
|
||||
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(), None))
|
||||
}
|
||||
let (domain, account) = match &build.config.image_registry {
|
||||
// Early return for None
|
||||
ImageRegistry::None(_) => return Ok((None, None)),
|
||||
// Early return for AwsEcr
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let config = core_config().aws_ecr_registries.get(label);
|
||||
let config = core_config()
|
||||
.aws_ecr_registries
|
||||
.iter()
|
||||
.find(|reg| ®.label == label);
|
||||
let token = match config {
|
||||
Some(AwsEcrConfigWithCredentials {
|
||||
region,
|
||||
@@ -830,10 +823,33 @@ async fn validate_account_extract_registry_token_aws_ecr(
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
Ok((token, config.map(AwsEcrConfig::from)))
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
Err(anyhow!("Custom image registry is not implemented"))
|
||||
return Ok((token, config.map(AwsEcrConfig::from)));
|
||||
}
|
||||
ImageRegistry::Standard(StandardRegistryConfig {
|
||||
domain,
|
||||
account,
|
||||
..
|
||||
}) => (domain.as_str(), account),
|
||||
};
|
||||
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must attach account to use registry provider {domain}"
|
||||
));
|
||||
}
|
||||
|
||||
Ok((
|
||||
core_config()
|
||||
.docker_registries
|
||||
.iter()
|
||||
.find(|provider| provider.domain == domain)
|
||||
.and_then(|provider| {
|
||||
provider
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|_account| &_account.username == account)
|
||||
.map(|account| account.token.clone())
|
||||
}),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use monitor_client::{
|
||||
entities::{
|
||||
build::{Build, ImageRegistry},
|
||||
config::core::AwsEcrConfig,
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
deployment::{
|
||||
extract_registry_domain, Deployment, DeploymentImage,
|
||||
},
|
||||
get_image_name,
|
||||
permission::PermissionLevel,
|
||||
server::ServerState,
|
||||
@@ -80,39 +82,101 @@ 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?;
|
||||
let image_name = get_image_name(&build, |label| {
|
||||
core_config()
|
||||
.aws_ecr_registries
|
||||
.get(label)
|
||||
.map(AwsEcrConfig::from)
|
||||
})
|
||||
.context("failed to create image name")?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
version
|
||||
};
|
||||
// replace image with corresponding build image.
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{version}"),
|
||||
};
|
||||
// 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;
|
||||
let (version, registry_token, aws_ecr) =
|
||||
match &deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(build_id).await?;
|
||||
let image_name = get_image_name(&build, |label| {
|
||||
core_config()
|
||||
.aws_ecr_registries
|
||||
.iter()
|
||||
.find(|reg| ®.label == label)
|
||||
.map(AwsEcrConfig::from)
|
||||
})
|
||||
.context("failed to create image name")?;
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
*version
|
||||
};
|
||||
// Remove ending patch if it is 0, this means use latest patch.
|
||||
let version_str = if version.patch == 0 {
|
||||
format!("{}.{}", version.major, version.minor)
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
// replace image with corresponding build image.
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{version_str}"),
|
||||
};
|
||||
match build.config.image_registry {
|
||||
ImageRegistry::None(_) => (version, None, None),
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let config = core_config()
|
||||
.aws_ecr_registries
|
||||
.iter()
|
||||
.find(|reg| reg.label == label)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"did not find config for aws ecr registry {label}"
|
||||
)
|
||||
})?;
|
||||
let token = ecr::get_ecr_token(
|
||||
&config.region,
|
||||
&config.access_key_id,
|
||||
&config.secret_access_key,
|
||||
)
|
||||
.await
|
||||
.context("failed to create aws ecr login token")?;
|
||||
(version, Some(token), Some(AwsEcrConfig::from(config)))
|
||||
}
|
||||
ImageRegistry::Standard(params) => {
|
||||
if deployment.config.image_registry_account.is_empty() {
|
||||
deployment.config.image_registry_account =
|
||||
params.account
|
||||
}
|
||||
let token = core_config()
|
||||
.docker_registries
|
||||
.iter()
|
||||
.find(|registry| registry.domain == params.domain)
|
||||
.and_then(|provider| {
|
||||
provider
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| {
|
||||
account.username
|
||||
== deployment.config.image_registry_account
|
||||
})
|
||||
.map(|account| account.token.clone())
|
||||
});
|
||||
(version, token, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
version
|
||||
}
|
||||
DeploymentImage::Image { .. } => Version::default(),
|
||||
};
|
||||
DeploymentImage::Image { image } => {
|
||||
let domain = extract_registry_domain(image)?;
|
||||
let token =
|
||||
(!deployment.config.image_registry_account.is_empty())
|
||||
.then(|| {
|
||||
core_config()
|
||||
.docker_registries
|
||||
.iter()
|
||||
.find(|registry| registry.domain == domain)
|
||||
.and_then(|provider| {
|
||||
provider
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| {
|
||||
account.username
|
||||
== deployment.config.image_registry_account
|
||||
})
|
||||
.map(|account| account.token.clone())
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
(Version::default(), token, None)
|
||||
}
|
||||
};
|
||||
|
||||
let variables = get_global_variables().await?;
|
||||
let core_config = core_config();
|
||||
@@ -169,46 +233,6 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
update.version = version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let (registry_token, aws_ecr) = match &deployment
|
||||
.config
|
||||
.image_registry
|
||||
{
|
||||
ImageRegistry::None(_) => (None, None),
|
||||
ImageRegistry::DockerHub(params) => (
|
||||
core_config.docker_accounts.get(¶ms.account).cloned(),
|
||||
None,
|
||||
),
|
||||
ImageRegistry::Ghcr(params) => (
|
||||
core_config.github_accounts.get(¶ms.account).cloned(),
|
||||
None,
|
||||
),
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let config = core_config
|
||||
.aws_ecr_registries
|
||||
.get(label)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"did not find config for aws ecr registry {label}"
|
||||
)
|
||||
})?;
|
||||
(
|
||||
Some(
|
||||
ecr::get_ecr_token(
|
||||
&config.region,
|
||||
&config.access_key_id,
|
||||
&config.secret_access_key,
|
||||
)
|
||||
.await
|
||||
.context("failed to create aws ecr login token")?,
|
||||
),
|
||||
Some(AwsEcrConfig::from(config)),
|
||||
)
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
return Err(anyhow!("Custom ImageRegistry not yet supported"))
|
||||
}
|
||||
};
|
||||
|
||||
match periphery
|
||||
.request(api::container::Deploy {
|
||||
deployment,
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
CloneRepo { repo }: CloneRepo,
|
||||
(user, mut update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
let mut repo = resource::get_check_permissions::<Repo>(
|
||||
&repo,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
@@ -57,15 +57,23 @@ impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
|
||||
let periphery = periphery_client(&server)?;
|
||||
|
||||
let github_token = core_config()
|
||||
.github_accounts
|
||||
.get(&repo.config.github_account)
|
||||
.cloned();
|
||||
let git_token = core_config()
|
||||
.git_providers
|
||||
.iter()
|
||||
.find(|provider| provider.domain == repo.config.git_provider)
|
||||
.and_then(|provider| {
|
||||
repo.config.git_https = provider.https;
|
||||
provider
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| account.username == repo.config.git_account)
|
||||
.map(|account| account.token.clone())
|
||||
});
|
||||
|
||||
let logs = match periphery
|
||||
.request(api::git::CloneRepo {
|
||||
args: (&repo).into(),
|
||||
github_token,
|
||||
git_token,
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
helpers::query::get_resource_ids_for_user,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
@@ -28,13 +28,13 @@ impl Resolve<ListAlerts, User> for State {
|
||||
) -> anyhow::Result<ListAlertsResponse> {
|
||||
let mut query = query.unwrap_or_default();
|
||||
if !user.admin && !core_config().transparent_mode {
|
||||
let server_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
let server_ids = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Server,
|
||||
)
|
||||
.await?;
|
||||
let deployment_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
let deployment_ids = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Deployment,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
@@ -10,12 +9,11 @@ use monitor_client::{
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, oid::ObjectId};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
helpers::query::get_resource_ids_for_user,
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
@@ -61,26 +59,21 @@ impl Resolve<GetAlertersSummary, User> for State {
|
||||
GetAlertersSummary {}: GetAlertersSummary,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetAlertersSummaryResponse> {
|
||||
let query = if user.admin || core_config().transparent_mode {
|
||||
None
|
||||
} else {
|
||||
let ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
ResourceTargetVariant::Alerter,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
let query = doc! {
|
||||
let query = match get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Alerter,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
"_id": { "$in": ids }
|
||||
};
|
||||
Some(query)
|
||||
},
|
||||
None => Document::new(),
|
||||
};
|
||||
let total = db_client()
|
||||
.await
|
||||
.alerters
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.count_documents(query)
|
||||
.await
|
||||
.context("failed to count all alerter documents")?;
|
||||
let res = GetAlertersSummaryResponse {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::OnceLock,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_timing_util::unix_timestamp_ms;
|
||||
@@ -10,6 +7,7 @@ use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
build::{Build, BuildActionState, BuildListItem, BuildState},
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
update::UpdateStatus,
|
||||
user::User,
|
||||
@@ -20,12 +18,14 @@ use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOptions},
|
||||
};
|
||||
use resolver_api::{Resolve, ResolveToString};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
resource,
|
||||
state::{action_states, build_state_cache, db_client, State},
|
||||
state::{
|
||||
action_states, build_state_cache, db_client, github_client, State,
|
||||
},
|
||||
};
|
||||
|
||||
impl Resolve<GetBuild, User> for State {
|
||||
@@ -190,16 +190,16 @@ fn ms_to_hour(duration: i64) -> f64 {
|
||||
duration as f64 / MS_TO_HOUR_DIVISOR
|
||||
}
|
||||
|
||||
impl Resolve<GetBuildVersions, User> for State {
|
||||
impl Resolve<ListBuildVersions, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuildVersions {
|
||||
ListBuildVersions {
|
||||
build,
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
limit,
|
||||
}: GetBuildVersions,
|
||||
}: ListBuildVersions,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<BuildVersionResponseItem>> {
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
@@ -247,42 +247,6 @@ 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(|| {
|
||||
serde_json::to_string(&core_config().docker_organizations)
|
||||
.expect("failed to serialize docker organizations")
|
||||
})
|
||||
}
|
||||
|
||||
impl ResolveToString<ListDockerOrganizations, User> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
ListDockerOrganizations {}: ListDockerOrganizations,
|
||||
_: User,
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(docker_organizations().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListCommonBuildExtraArgs, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
@@ -307,3 +271,78 @@ impl Resolve<ListCommonBuildExtraArgs, User> for State {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetBuildWebhookEnabled, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuildWebhookEnabled { build }: GetBuildWebhookEnabled,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetBuildWebhookEnabledResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Ok(GetBuildWebhookEnabledResponse {
|
||||
managed: false,
|
||||
enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&build,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if build.config.git_provider != "github.com"
|
||||
|| build.config.repo.is_empty()
|
||||
{
|
||||
return Ok(GetBuildWebhookEnabledResponse {
|
||||
managed: false,
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
let mut split = build.config.repo.split('/');
|
||||
let owner = split.next().context("Build repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Ok(GetBuildWebhookEnabledResponse {
|
||||
managed: false,
|
||||
enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let repo =
|
||||
split.next().context("Build repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = format!("{host}/listener/github/build/{}", build.id);
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
return Ok(GetBuildWebhookEnabledResponse {
|
||||
managed: true,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetBuildWebhookEnabledResponse {
|
||||
managed: true,
|
||||
enabled: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::{
|
||||
api::read::{self, *},
|
||||
api::read::*,
|
||||
entities::{
|
||||
builder::{Builder, BuilderConfig, BuilderListItem},
|
||||
builder::{Builder, BuilderListItem},
|
||||
permission::PermissionLevel,
|
||||
update::ResourceTargetVariant,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, oid::ObjectId};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
helpers::query::get_resource_ids_for_user,
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
@@ -61,26 +59,21 @@ impl Resolve<GetBuildersSummary, User> for State {
|
||||
GetBuildersSummary {}: GetBuildersSummary,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetBuildersSummaryResponse> {
|
||||
let query = if user.admin || core_config().transparent_mode {
|
||||
None
|
||||
} else {
|
||||
let ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
ResourceTargetVariant::Builder,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
let query = doc! {
|
||||
let query = match get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Builder,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
"_id": { "$in": ids }
|
||||
};
|
||||
Some(query)
|
||||
},
|
||||
None => Document::new(),
|
||||
};
|
||||
let total = db_client()
|
||||
.await
|
||||
.builders
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.count_documents(query)
|
||||
.await
|
||||
.context("failed to count all builder documents")?;
|
||||
let res = GetBuildersSummaryResponse {
|
||||
@@ -89,52 +82,3 @@ impl Resolve<GetBuildersSummary, User> for State {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetBuilderAvailableAccounts, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuilderAvailableAccounts { builder }: GetBuilderAvailableAccounts,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetBuilderAvailableAccountsResponse> {
|
||||
let builder = resource::get_check_permissions::<Builder>(
|
||||
&builder,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let (github, docker) = match builder.config {
|
||||
BuilderConfig::Aws(config) => {
|
||||
(config.github_accounts, config.docker_accounts)
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
let res = self
|
||||
.resolve(
|
||||
read::GetAvailableAccounts {
|
||||
server: Some(config.server_id),
|
||||
},
|
||||
user,
|
||||
)
|
||||
.await?;
|
||||
(res.github, res.docker)
|
||||
}
|
||||
};
|
||||
|
||||
let mut github_set = HashSet::<String>::new();
|
||||
|
||||
github_set.extend(core_config().github_accounts.keys().cloned());
|
||||
github_set.extend(github);
|
||||
|
||||
let mut github = github_set.into_iter().collect::<Vec<_>>();
|
||||
github.sort();
|
||||
|
||||
let mut docker_set = HashSet::<String>::new();
|
||||
|
||||
docker_set.extend(core_config().docker_accounts.keys().cloned());
|
||||
docker_set.extend(docker);
|
||||
|
||||
let mut docker = docker_set.into_iter().collect::<Vec<_>>();
|
||||
docker.sort();
|
||||
|
||||
Ok(GetBuilderAvailableAccountsResponse { github, docker })
|
||||
}
|
||||
}
|
||||
|
||||
+366
-48
@@ -1,16 +1,33 @@
|
||||
use std::time::Instant;
|
||||
use std::{collections::HashSet, sync::OnceLock, time::Instant};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{middleware, routing::post, Extension, Router};
|
||||
use axum_extra::{headers::ContentType, TypedHeader};
|
||||
use monitor_client::{api::read::*, entities::user::User};
|
||||
use resolver_api::{derive::Resolver, Resolve, Resolver};
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
config::{DockerRegistry, GitProvider},
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
sync::ResourceSync,
|
||||
update::ResourceTarget,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::{
|
||||
derive::Resolver, Resolve, ResolveToString, Resolver,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serror::Json;
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{auth::auth_request, config::core_config, state::State};
|
||||
use crate::{
|
||||
auth::auth_request, config::core_config, helpers::periphery_client,
|
||||
resource, state::State,
|
||||
};
|
||||
|
||||
mod alert;
|
||||
mod alerter;
|
||||
@@ -37,17 +54,24 @@ mod variable;
|
||||
#[resolver_args(User)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
enum ReadRequest {
|
||||
#[to_string_resolver]
|
||||
GetVersion(GetVersion),
|
||||
#[to_string_resolver]
|
||||
GetCoreInfo(GetCoreInfo),
|
||||
GetAvailableAwsEcrLabels(GetAvailableAwsEcrLabels),
|
||||
#[to_string_resolver]
|
||||
ListAwsEcrLabels(ListAwsEcrLabels),
|
||||
ListSecrets(ListSecrets),
|
||||
ListGitProviders(ListGitProviders),
|
||||
ListDockerRegistries(ListDockerRegistries),
|
||||
|
||||
// ==== USER ====
|
||||
ListUsers(ListUsers),
|
||||
GetUsername(GetUsername),
|
||||
GetPermissionLevel(GetPermissionLevel),
|
||||
FindUser(FindUser),
|
||||
ListUsers(ListUsers),
|
||||
ListApiKeys(ListApiKeys),
|
||||
ListApiKeysForServiceUser(ListApiKeysForServiceUser),
|
||||
ListPermissions(ListPermissions),
|
||||
GetPermissionLevel(GetPermissionLevel),
|
||||
ListUserTargetPermissions(ListUserTargetPermissions),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
@@ -66,15 +90,13 @@ enum ReadRequest {
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
GetServerTemplate(GetServerTemplate),
|
||||
GetServerTemplatesSummary(GetServerTemplatesSummary),
|
||||
ListServerTemplates(ListServerTemplates),
|
||||
ListFullServerTemplates(ListFullServerTemplates),
|
||||
GetServerTemplatesSummary(GetServerTemplatesSummary),
|
||||
|
||||
// ==== SERVER ====
|
||||
GetServersSummary(GetServersSummary),
|
||||
GetServer(GetServer),
|
||||
ListServers(ListServers),
|
||||
ListFullServers(ListFullServers),
|
||||
GetServerState(GetServerState),
|
||||
GetPeripheryVersion(GetPeripheryVersion),
|
||||
GetDockerContainers(GetDockerContainers),
|
||||
@@ -82,55 +104,53 @@ enum ReadRequest {
|
||||
GetDockerNetworks(GetDockerNetworks),
|
||||
GetServerActionState(GetServerActionState),
|
||||
GetHistoricalServerStats(GetHistoricalServerStats),
|
||||
GetAvailableAccounts(GetAvailableAccounts),
|
||||
GetAvailableSecrets(GetAvailableSecrets),
|
||||
ListServers(ListServers),
|
||||
ListFullServers(ListFullServers),
|
||||
|
||||
// ==== DEPLOYMENT ====
|
||||
GetDeploymentsSummary(GetDeploymentsSummary),
|
||||
GetDeployment(GetDeployment),
|
||||
ListDeployments(ListDeployments),
|
||||
ListFullDeployments(ListFullDeployments),
|
||||
GetDeploymentContainer(GetDeploymentContainer),
|
||||
GetDeploymentActionState(GetDeploymentActionState),
|
||||
GetDeploymentStats(GetDeploymentStats),
|
||||
GetLog(GetLog),
|
||||
SearchLog(SearchLog),
|
||||
ListDeployments(ListDeployments),
|
||||
ListFullDeployments(ListFullDeployments),
|
||||
ListCommonDeploymentExtraArgs(ListCommonDeploymentExtraArgs),
|
||||
|
||||
// ==== BUILD ====
|
||||
GetBuildsSummary(GetBuildsSummary),
|
||||
GetBuild(GetBuild),
|
||||
ListBuilds(ListBuilds),
|
||||
ListFullBuilds(ListFullBuilds),
|
||||
GetBuildActionState(GetBuildActionState),
|
||||
GetBuildMonthlyStats(GetBuildMonthlyStats),
|
||||
GetBuildVersions(GetBuildVersions),
|
||||
ListBuildVersions(ListBuildVersions),
|
||||
GetBuildWebhookEnabled(GetBuildWebhookEnabled),
|
||||
ListBuilds(ListBuilds),
|
||||
ListFullBuilds(ListFullBuilds),
|
||||
ListCommonBuildExtraArgs(ListCommonBuildExtraArgs),
|
||||
#[to_string_resolver]
|
||||
ListGithubOrganizations(ListGithubOrganizations),
|
||||
#[to_string_resolver]
|
||||
ListDockerOrganizations(ListDockerOrganizations),
|
||||
|
||||
// ==== REPO ====
|
||||
GetReposSummary(GetReposSummary),
|
||||
GetRepo(GetRepo),
|
||||
GetRepoActionState(GetRepoActionState),
|
||||
GetRepoWebhooksEnabled(GetRepoWebhooksEnabled),
|
||||
ListRepos(ListRepos),
|
||||
ListFullRepos(ListFullRepos),
|
||||
GetRepoActionState(GetRepoActionState),
|
||||
|
||||
// ==== SYNC ====
|
||||
GetResourceSyncsSummary(GetResourceSyncsSummary),
|
||||
GetResourceSync(GetResourceSync),
|
||||
GetResourceSyncActionState(GetResourceSyncActionState),
|
||||
GetSyncWebhooksEnabled(GetSyncWebhooksEnabled),
|
||||
ListResourceSyncs(ListResourceSyncs),
|
||||
ListFullResourceSyncs(ListFullResourceSyncs),
|
||||
GetResourceSyncActionState(GetResourceSyncActionState),
|
||||
|
||||
// ==== BUILDER ====
|
||||
GetBuildersSummary(GetBuildersSummary),
|
||||
GetBuilder(GetBuilder),
|
||||
ListBuilders(ListBuilders),
|
||||
ListFullBuilders(ListFullBuilders),
|
||||
GetBuilderAvailableAccounts(GetBuilderAvailableAccounts),
|
||||
|
||||
// ==== ALERTER ====
|
||||
GetAlertersSummary(GetAlertersSummary),
|
||||
@@ -199,46 +219,344 @@ async fn handler(
|
||||
Ok((TypedHeader(ContentType::json()), res?))
|
||||
}
|
||||
|
||||
impl Resolve<GetVersion, User> for State {
|
||||
#[instrument(name = "GetVersion", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
fn version() -> &'static String {
|
||||
static VERSION: OnceLock<String> = OnceLock::new();
|
||||
VERSION.get_or_init(|| {
|
||||
serde_json::to_string(&GetVersionResponse {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
})
|
||||
.context("failed to serialize GetVersionResponse")
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
impl ResolveToString<GetVersion, User> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetVersion {}: GetVersion,
|
||||
_: User,
|
||||
) -> anyhow::Result<GetVersionResponse> {
|
||||
Ok(GetVersionResponse {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
})
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(version().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetCoreInfo, User> for State {
|
||||
#[instrument(name = "GetCoreInfo", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetCoreInfo {}: GetCoreInfo,
|
||||
_: User,
|
||||
) -> anyhow::Result<GetCoreInfoResponse> {
|
||||
fn core_info() -> &'static String {
|
||||
static CORE_INFO: OnceLock<String> = OnceLock::new();
|
||||
CORE_INFO.get_or_init(|| {
|
||||
let config = core_config();
|
||||
Ok(GetCoreInfoResponse {
|
||||
let info = GetCoreInfoResponse {
|
||||
title: config.title.clone(),
|
||||
monitoring_interval: config.monitoring_interval,
|
||||
github_webhook_base_url: config
|
||||
.github_webhook_base_url
|
||||
webhook_base_url: config
|
||||
.webhook_base_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| config.host.clone()),
|
||||
transparent_mode: config.transparent_mode,
|
||||
ui_write_disabled: config.ui_write_disabled,
|
||||
})
|
||||
github_webhook_owners: config
|
||||
.github_webhook_app
|
||||
.installations
|
||||
.iter()
|
||||
.map(|i| i.namespace.to_string())
|
||||
.collect(),
|
||||
};
|
||||
serde_json::to_string(&info)
|
||||
.context("failed to serialize GetCoreInfoResponse")
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
impl ResolveToString<GetCoreInfo, User> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetCoreInfo {}: GetCoreInfo,
|
||||
_: User,
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(core_info().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetAvailableAwsEcrLabels, User> for State {
|
||||
async fn resolve(
|
||||
fn ecr_labels() -> &'static String {
|
||||
static ECR_LABELS: OnceLock<String> = OnceLock::new();
|
||||
ECR_LABELS.get_or_init(|| {
|
||||
serde_json::to_string(
|
||||
&core_config()
|
||||
.aws_ecr_registries
|
||||
.iter()
|
||||
.map(|reg| reg.label.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.context("failed to serialize ecr registries")
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
impl ResolveToString<ListAwsEcrLabels, User> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetAvailableAwsEcrLabels {}: GetAvailableAwsEcrLabels,
|
||||
ListAwsEcrLabels {}: ListAwsEcrLabels,
|
||||
_: User,
|
||||
) -> anyhow::Result<GetAvailableAwsEcrLabelsResponse> {
|
||||
Ok(core_config().aws_ecr_registries.keys().cloned().collect())
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(ecr_labels().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListSecrets, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListSecrets { target }: ListSecrets,
|
||||
_: User,
|
||||
) -> anyhow::Result<ListSecretsResponse> {
|
||||
let mut secrets = core_config()
|
||||
.secrets
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
if let Some(target) = target {
|
||||
let server_id = match target {
|
||||
ResourceTarget::Server(id) => Some(id),
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Server(config) => Some(config.server_id),
|
||||
BuilderConfig::Aws(config) => {
|
||||
secrets.extend(config.secrets);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!("target must be `Server` or `Builder`"))
|
||||
}
|
||||
};
|
||||
if let Some(id) = server_id {
|
||||
let server = resource::get::<Server>(&id).await?;
|
||||
let more = periphery_client(&server)?
|
||||
.request(periphery_client::api::ListSecrets {})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to get secrets from server {}",
|
||||
server.name
|
||||
)
|
||||
})?;
|
||||
secrets.extend(more);
|
||||
}
|
||||
}
|
||||
|
||||
let mut secrets = secrets.into_iter().collect::<Vec<_>>();
|
||||
secrets.sort();
|
||||
|
||||
Ok(secrets)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListGitProviders, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListGitProviders { target }: ListGitProviders,
|
||||
user: User,
|
||||
) -> anyhow::Result<ListGitProvidersResponse> {
|
||||
let mut providers = core_config().git_providers.clone();
|
||||
|
||||
if let Some(target) = target {
|
||||
match target {
|
||||
ResourceTarget::Server(id) => {
|
||||
merge_git_providers_for_server(&mut providers, &id).await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Server(config) => {
|
||||
merge_git_providers_for_server(
|
||||
&mut providers,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_git_providers(
|
||||
&mut providers,
|
||||
config.git_providers,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!("target must be `Server` or `Builder`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (builds, repos, syncs) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
&user
|
||||
),
|
||||
resource::list_full_for_user::<Repo>(Default::default(), &user),
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
&user
|
||||
),
|
||||
)?;
|
||||
|
||||
for build in builds {
|
||||
if !providers
|
||||
.iter()
|
||||
.any(|provider| provider.domain == build.config.git_provider)
|
||||
{
|
||||
providers.push(GitProvider {
|
||||
domain: build.config.git_provider,
|
||||
https: build.config.git_https,
|
||||
accounts: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
for repo in repos {
|
||||
if !providers
|
||||
.iter()
|
||||
.any(|provider| provider.domain == repo.config.git_provider)
|
||||
{
|
||||
providers.push(GitProvider {
|
||||
domain: repo.config.git_provider,
|
||||
https: repo.config.git_https,
|
||||
accounts: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
for sync in syncs {
|
||||
if !providers
|
||||
.iter()
|
||||
.any(|provider| provider.domain == sync.config.git_provider)
|
||||
{
|
||||
providers.push(GitProvider {
|
||||
domain: sync.config.git_provider,
|
||||
https: sync.config.git_https,
|
||||
accounts: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
providers.sort();
|
||||
|
||||
Ok(providers)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListDockerRegistries, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListDockerRegistries { target }: ListDockerRegistries,
|
||||
_: User,
|
||||
) -> anyhow::Result<ListDockerRegistriesResponse> {
|
||||
let mut registries = core_config().docker_registries.clone();
|
||||
|
||||
if let Some(target) = target {
|
||||
match target {
|
||||
ResourceTarget::Server(id) => {
|
||||
merge_docker_registries_for_server(&mut registries, &id)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
match resource::get::<Builder>(&id).await?.config {
|
||||
BuilderConfig::Server(config) => {
|
||||
merge_docker_registries_for_server(
|
||||
&mut registries,
|
||||
&config.server_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
merge_docker_registries(
|
||||
&mut registries,
|
||||
config.docker_registries,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!("target must be `Server` or `Builder`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registries.sort();
|
||||
|
||||
Ok(registries)
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge_git_providers_for_server(
|
||||
providers: &mut Vec<GitProvider>,
|
||||
server_id: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = resource::get::<Server>(server_id).await?;
|
||||
let more = periphery_client(&server)?
|
||||
.request(periphery_client::api::ListGitProviders {})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to get git providers from server {}",
|
||||
server.name
|
||||
)
|
||||
})?;
|
||||
merge_git_providers(providers, more);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_git_providers(
|
||||
providers: &mut Vec<GitProvider>,
|
||||
more: Vec<GitProvider>,
|
||||
) {
|
||||
for incoming_provider in more {
|
||||
if let Some(provider) = providers
|
||||
.iter_mut()
|
||||
.find(|provider| provider.domain == incoming_provider.domain)
|
||||
{
|
||||
for account in incoming_provider.accounts {
|
||||
if !provider.accounts.contains(&account) {
|
||||
provider.accounts.push(account);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
providers.push(incoming_provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge_docker_registries_for_server(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
server_id: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let server = resource::get::<Server>(server_id).await?;
|
||||
let more = periphery_client(&server)?
|
||||
.request(periphery_client::api::ListDockerRegistries {})
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to get docker registries from server {}",
|
||||
server.name
|
||||
)
|
||||
})?;
|
||||
merge_docker_registries(registries, more);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_docker_registries(
|
||||
registries: &mut Vec<DockerRegistry>,
|
||||
more: Vec<DockerRegistry>,
|
||||
) {
|
||||
for incoming_registry in more {
|
||||
if let Some(registry) = registries
|
||||
.iter_mut()
|
||||
.find(|registry| registry.domain == incoming_registry.domain)
|
||||
{
|
||||
for account in incoming_registry.accounts {
|
||||
if !registry.accounts.contains(&account) {
|
||||
registry.accounts.push(account);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
registries.push(incoming_registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Resolve<GetPermissionLevel, User> for State {
|
||||
return Ok(PermissionLevel::Write);
|
||||
}
|
||||
let (variant, id) = target.extract_variant_id();
|
||||
get_user_permission_on_resource(&user.id, variant, id).await
|
||||
get_user_permission_on_resource(&user, variant, id).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
repo::{Repo, RepoActionState, RepoListItem, RepoState},
|
||||
user::User,
|
||||
@@ -10,8 +11,9 @@ use monitor_client::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
resource,
|
||||
state::{action_states, repo_state_cache, State},
|
||||
state::{action_states, github_client, repo_state_cache, State},
|
||||
};
|
||||
|
||||
impl Resolve<GetRepo, User> for State {
|
||||
@@ -118,3 +120,88 @@ impl Resolve<GetReposSummary, User> for State {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetRepoWebhooksEnabled, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetRepoWebhooksEnabled { repo }: GetRepoWebhooksEnabled,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetRepoWebhooksEnabledResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Ok(GetRepoWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
clone_enabled: false,
|
||||
pull_enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
&repo,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if repo.config.git_provider != "github.com"
|
||||
|| repo.config.repo.is_empty()
|
||||
{
|
||||
return Ok(GetRepoWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
clone_enabled: false,
|
||||
pull_enabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
let mut split = repo.config.repo.split('/');
|
||||
let owner = split.next().context("Repo repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Ok(GetRepoWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
clone_enabled: false,
|
||||
pull_enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let repo_name =
|
||||
split.next().context("Repo repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo_name)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let clone_url =
|
||||
format!("{host}/listener/github/repo/{}/clone", repo.id);
|
||||
let pull_url =
|
||||
format!("{host}/listener/github/repo/{}/pull", repo.id);
|
||||
|
||||
let mut clone_enabled = false;
|
||||
let mut pull_enabled = false;
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == clone_url {
|
||||
clone_enabled = true
|
||||
}
|
||||
if webhook.active && webhook.config.url == pull_url {
|
||||
pull_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetRepoWebhooksEnabledResponse {
|
||||
managed: true,
|
||||
clone_enabled,
|
||||
pull_enabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
@@ -23,12 +23,11 @@ use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOptions},
|
||||
};
|
||||
use periphery_client::api::{self, GetAccountsResponse};
|
||||
use periphery_client::api as periphery;
|
||||
use resolver_api::{Resolve, ResolveToString};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::periphery_client,
|
||||
resource,
|
||||
state::{action_states, db_client, server_status_cache, State},
|
||||
@@ -192,7 +191,7 @@ impl ResolveToString<GetSystemInformation, User> for State {
|
||||
}
|
||||
_ => {
|
||||
let stats = periphery_client(&server)?
|
||||
.request(api::stats::GetSystemInformation {})
|
||||
.request(periphery::stats::GetSystemInformation {})
|
||||
.await?;
|
||||
let res = serde_json::to_string(&stats)?;
|
||||
lock.insert(
|
||||
@@ -259,7 +258,7 @@ impl ResolveToString<GetSystemProcesses, User> for State {
|
||||
}
|
||||
_ => {
|
||||
let stats = periphery_client(&server)?
|
||||
.request(api::stats::GetSystemProcesses {})
|
||||
.request(periphery::stats::GetSystemProcesses {})
|
||||
.await?;
|
||||
let res = serde_json::to_string(&stats)?;
|
||||
lock.insert(
|
||||
@@ -342,7 +341,7 @@ impl Resolve<GetDockerImages, User> for State {
|
||||
)
|
||||
.await?;
|
||||
periphery_client(&server)?
|
||||
.request(api::build::GetImageList {})
|
||||
.request(periphery::build::GetImageList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -360,7 +359,7 @@ impl Resolve<GetDockerNetworks, User> for State {
|
||||
)
|
||||
.await?;
|
||||
periphery_client(&server)?
|
||||
.request(api::network::GetNetworkList {})
|
||||
.request(periphery::network::GetNetworkList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -378,74 +377,7 @@ impl Resolve<GetDockerContainers, User> for State {
|
||||
)
|
||||
.await?;
|
||||
periphery_client(&server)?
|
||||
.request(api::container::GetContainerList {})
|
||||
.request(periphery::container::GetContainerList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetAvailableAccounts, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetAvailableAccounts { server }: GetAvailableAccounts,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetAvailableAccountsResponse> {
|
||||
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")?;
|
||||
(github, docker)
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let mut github_set = HashSet::<String>::new();
|
||||
|
||||
github_set.extend(core_config().github_accounts.keys().cloned());
|
||||
github_set.extend(github);
|
||||
|
||||
let mut github = github_set.into_iter().collect::<Vec<_>>();
|
||||
github.sort();
|
||||
|
||||
let mut docker_set = HashSet::<String>::new();
|
||||
|
||||
docker_set.extend(core_config().docker_accounts.keys().cloned());
|
||||
docker_set.extend(docker);
|
||||
|
||||
let mut docker = docker_set.into_iter().collect::<Vec<_>>();
|
||||
docker.sort();
|
||||
|
||||
let res = GetAvailableAccountsResponse { github, docker };
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetAvailableSecrets, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetAvailableSecrets { server }: GetAvailableSecrets,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetAvailableSecretsResponse> {
|
||||
let server = resource::get_check_permissions::<Server>(
|
||||
&server,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let mut secrets = periphery_client(&server)?
|
||||
.request(api::GetSecrets {})
|
||||
.await
|
||||
.context("failed to get accounts from periphery")?;
|
||||
secrets.sort();
|
||||
Ok(secrets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Context;
|
||||
use mongo_indexed::Document;
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
@@ -8,11 +7,11 @@ use monitor_client::{
|
||||
update::ResourceTargetVariant, user::User,
|
||||
},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, oid::ObjectId};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
helpers::query::get_resource_ids_for_user,
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
@@ -58,26 +57,21 @@ impl Resolve<GetServerTemplatesSummary, User> for State {
|
||||
GetServerTemplatesSummary {}: GetServerTemplatesSummary,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetServerTemplatesSummaryResponse> {
|
||||
let query = if user.admin {
|
||||
None
|
||||
} else {
|
||||
let ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
ResourceTargetVariant::ServerTemplate,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
let query = doc! {
|
||||
let query = match get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::ServerTemplate,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
"_id": { "$in": ids }
|
||||
};
|
||||
Some(query)
|
||||
},
|
||||
None => Document::new(),
|
||||
};
|
||||
let total = db_client()
|
||||
.await
|
||||
.server_templates
|
||||
.count_documents(query.unwrap_or_default())
|
||||
.count_documents(query)
|
||||
.await
|
||||
.context("failed to count all server template documents")?;
|
||||
let res = GetServerTemplatesSummaryResponse {
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
use monitor_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
sync::{
|
||||
PendingSyncUpdatesData, ResourceSync, ResourceSyncActionState,
|
||||
@@ -13,8 +14,11 @@ use monitor_client::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
resource,
|
||||
state::{action_states, resource_sync_state_cache, State},
|
||||
state::{
|
||||
action_states, github_client, resource_sync_state_cache, State,
|
||||
},
|
||||
};
|
||||
|
||||
impl Resolve<GetResourceSync, User> for State {
|
||||
@@ -137,3 +141,88 @@ impl Resolve<GetResourceSyncsSummary, User> for State {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetSyncWebhooksEnabled, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetSyncWebhooksEnabled { sync }: GetSyncWebhooksEnabled,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetSyncWebhooksEnabledResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Ok(GetSyncWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
refresh_enabled: false,
|
||||
sync_enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&sync,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if sync.config.git_provider != "github.com"
|
||||
|| sync.config.repo.is_empty()
|
||||
{
|
||||
return Ok(GetSyncWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
refresh_enabled: false,
|
||||
sync_enabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
let mut split = sync.config.repo.split('/');
|
||||
let owner = split.next().context("Sync repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Ok(GetSyncWebhooksEnabledResponse {
|
||||
managed: false,
|
||||
refresh_enabled: false,
|
||||
sync_enabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
let repo_name =
|
||||
split.next().context("Repo repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo_name)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let refresh_url =
|
||||
format!("{host}/listener/github/sync/{}/refresh", sync.id);
|
||||
let sync_url =
|
||||
format!("{host}/listener/github/sync/{}/sync", sync.id);
|
||||
|
||||
let mut refresh_enabled = false;
|
||||
let mut sync_enabled = false;
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == refresh_url {
|
||||
refresh_enabled = true
|
||||
}
|
||||
if webhook.active && webhook.config.url == sync_url {
|
||||
sync_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetSyncWebhooksEnabledResponse {
|
||||
managed: true,
|
||||
refresh_enabled,
|
||||
sync_enabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
};
|
||||
}
|
||||
|
||||
add_user_groups(user_groups, &mut res, &user)
|
||||
add_user_groups(user_groups, &mut res, &names, &user)
|
||||
.await
|
||||
.context("failed to add user groups")?;
|
||||
|
||||
@@ -424,6 +424,8 @@ struct ResourceNames {
|
||||
deployments: HashMap<String, String>,
|
||||
procedures: HashMap<String, String>,
|
||||
syncs: HashMap<String, String>,
|
||||
alerters: HashMap<String, String>,
|
||||
templates: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ResourceNames {
|
||||
@@ -478,6 +480,18 @@ impl ResourceNames {
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
alerters: find_collect(&db.alerters, None, None)
|
||||
.await
|
||||
.context("failed to get all alerters")?
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
templates: find_collect(&db.server_templates, None, None)
|
||||
.await
|
||||
.context("failed to get all server templates")?
|
||||
.into_iter()
|
||||
.map(|t| (t.id, t.name))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -485,6 +499,7 @@ impl ResourceNames {
|
||||
async fn add_user_groups(
|
||||
user_groups: Vec<String>,
|
||||
res: &mut ResourcesToml,
|
||||
names: &ResourceNames,
|
||||
user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
let db = db_client().await;
|
||||
@@ -512,9 +527,43 @@ async fn add_user_groups(
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|permission| PermissionToml {
|
||||
target: permission.resource_target,
|
||||
level: permission.level,
|
||||
.map(|mut permission| {
|
||||
match &mut permission.resource_target {
|
||||
ResourceTarget::Build(id) => {
|
||||
*id = names.builds.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Builder(id) => {
|
||||
*id = names.builders.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
*id =
|
||||
names.deployments.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Server(id) => {
|
||||
*id = names.servers.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
*id = names.repos.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Alerter(id) => {
|
||||
*id = names.alerters.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
*id =
|
||||
names.procedures.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = names.templates.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
*id = names.syncs.get(id).cloned().unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::System(_) => {}
|
||||
}
|
||||
PermissionToml {
|
||||
target: permission.resource_target,
|
||||
level: permission.level,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
res.user_groups.push(UserGroupToml {
|
||||
@@ -524,6 +573,7 @@ async fn add_user_groups(
|
||||
.into_iter()
|
||||
.filter_map(|user_id| usernames.get(&user_id).cloned())
|
||||
.collect(),
|
||||
all: ug.all,
|
||||
permissions,
|
||||
});
|
||||
}
|
||||
|
||||
+101
-35
@@ -29,7 +29,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_resource_ids_for_non_admin,
|
||||
helpers::query::get_resource_ids_for_user,
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
};
|
||||
@@ -45,58 +45,124 @@ impl Resolve<ListUpdates, User> for State {
|
||||
let query = if user.admin || core_config().transparent_mode {
|
||||
query
|
||||
} else {
|
||||
let server_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
let server_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Server,
|
||||
)
|
||||
.await?;
|
||||
let deployment_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Server", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Server" });
|
||||
|
||||
let deployment_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Deployment,
|
||||
)
|
||||
.await?;
|
||||
let build_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Deployment", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
|
||||
|
||||
let build_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Build,
|
||||
)
|
||||
.await?;
|
||||
let repo_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
ResourceTargetVariant::Repo,
|
||||
)
|
||||
.await?;
|
||||
let procedure_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Build", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Build" });
|
||||
|
||||
let repo_query =
|
||||
get_resource_ids_for_user(&user, ResourceTargetVariant::Repo)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Repo", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Repo" });
|
||||
|
||||
let procedure_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Procedure,
|
||||
)
|
||||
.await?;
|
||||
let builder_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Procedure", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
let builder_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Builder,
|
||||
)
|
||||
.await?;
|
||||
let alerter_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Builder", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Builder" });
|
||||
|
||||
let alerter_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::Alerter,
|
||||
)
|
||||
.await?;
|
||||
let server_template_ids = get_resource_ids_for_non_admin(
|
||||
&user.id,
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Alerter", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
|
||||
|
||||
let server_template_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::ServerTemplate,
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "ServerTemplate", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "ServerTemplate" });
|
||||
|
||||
let resource_sync_query = get_resource_ids_for_user(
|
||||
&user,
|
||||
ResourceTargetVariant::ResourceSync,
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "ResourceSync", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
|
||||
|
||||
let mut query = query.unwrap_or_default();
|
||||
query.extend(doc! {
|
||||
"$or": [
|
||||
{ "target.type": "Server", "target.id": { "$in": &server_ids } },
|
||||
{ "target.type": "Deployment", "target.id": { "$in": &deployment_ids } },
|
||||
{ "target.type": "Build", "target.id": { "$in": &build_ids } },
|
||||
{ "target.type": "Repo", "target.id": { "$in": &repo_ids } },
|
||||
{ "target.type": "Procedure", "target.id": { "$in": &procedure_ids } },
|
||||
{ "target.type": "Builder", "target.id": { "$in": &builder_ids } },
|
||||
{ "target.type": "Alerter", "target.id": { "$in": &alerter_ids } },
|
||||
{ "target.type": "ServerTemplate", "target.id": { "$in": &server_template_ids } },
|
||||
server_query,
|
||||
build_query,
|
||||
deployment_query,
|
||||
repo_query,
|
||||
procedure_query,
|
||||
alerter_query,
|
||||
builder_query,
|
||||
server_template_query,
|
||||
resource_sync_query,
|
||||
]
|
||||
});
|
||||
query.into()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::read::{
|
||||
GetUsername, GetUsernameResponse, ListApiKeys,
|
||||
ListApiKeysForServiceUser, ListApiKeysForServiceUserResponse,
|
||||
ListApiKeysResponse, ListUsers, ListUsersResponse,
|
||||
FindUser, FindUserResponse, GetUsername, GetUsernameResponse,
|
||||
ListApiKeys, ListApiKeysForServiceUser,
|
||||
ListApiKeysForServiceUserResponse, ListApiKeysResponse,
|
||||
ListUsers, ListUsersResponse,
|
||||
},
|
||||
entities::user::{User, UserConfig},
|
||||
};
|
||||
@@ -14,7 +15,10 @@ use mungos::{
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
use crate::{
|
||||
helpers::query::get_user,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<GetUsername, User> for State {
|
||||
async fn resolve(
|
||||
@@ -40,6 +44,19 @@ impl Resolve<GetUsername, User> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<FindUser, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
FindUser { user }: FindUser,
|
||||
admin: User,
|
||||
) -> anyhow::Result<FindUserResponse> {
|
||||
if !admin.admin {
|
||||
return Err(anyhow!("This method is admin only."));
|
||||
}
|
||||
get_user(&user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListUsers, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
@@ -87,22 +104,21 @@ impl Resolve<ListApiKeys, User> for State {
|
||||
impl Resolve<ListApiKeysForServiceUser, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListApiKeysForServiceUser { user_id }: ListApiKeysForServiceUser,
|
||||
ListApiKeysForServiceUser { user }: ListApiKeysForServiceUser,
|
||||
admin: User,
|
||||
) -> anyhow::Result<ListApiKeysForServiceUserResponse> {
|
||||
if !admin.admin {
|
||||
return Err(anyhow!("This method is admin only."));
|
||||
}
|
||||
let user = find_one_by_id(&db_client().await.users, &user_id)
|
||||
.await
|
||||
.context("failed to query db for users")?
|
||||
.context("user at id not found")?;
|
||||
|
||||
let user = get_user(&user).await?;
|
||||
|
||||
let UserConfig::Service { .. } = user.config else {
|
||||
return Err(anyhow!("Given user is not service user"));
|
||||
};
|
||||
let api_keys = find_collect(
|
||||
&db_client().await.api_keys,
|
||||
doc! { "user_id": user_id },
|
||||
doc! { "user_id": &user.id },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -11,7 +11,6 @@ use mungos::{find::find_collect, mongodb::options::FindOptions};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_variable,
|
||||
state::{db_client, State},
|
||||
};
|
||||
@@ -32,16 +31,12 @@ impl Resolve<ListVariables, User> for State {
|
||||
ListVariables {}: ListVariables,
|
||||
_: User,
|
||||
) -> anyhow::Result<ListVariablesResponse> {
|
||||
let variables = find_collect(
|
||||
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(),
|
||||
})
|
||||
.context("failed to query db for variables")
|
||||
}
|
||||
}
|
||||
|
||||
+16
-29
@@ -11,10 +11,7 @@ use monitor_client::{
|
||||
PushRecentlyViewedResponse, SetLastSeenUpdate,
|
||||
SetLastSeenUpdateResponse,
|
||||
},
|
||||
entities::{
|
||||
api_key::ApiKey, monitor_timestamp, update::ResourceTarget,
|
||||
user::User,
|
||||
},
|
||||
entities::{api_key::ApiKey, monitor_timestamp, user::User},
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_bson};
|
||||
use resolver_api::{derive::Resolver, Resolve, Resolver};
|
||||
@@ -90,33 +87,21 @@ impl Resolve<PushRecentlyViewed, User> for State {
|
||||
) -> anyhow::Result<PushRecentlyViewedResponse> {
|
||||
let user = get_user(&user.id).await?;
|
||||
|
||||
let (recents, id, field) = match resource {
|
||||
ResourceTarget::Server(id) => {
|
||||
(user.recent_servers, id, "recent_servers")
|
||||
let (resource_type, id) = resource.extract_variant_id();
|
||||
let update = match user.recents.get(&resource_type) {
|
||||
Some(recents) => {
|
||||
let mut recents = recents
|
||||
.iter()
|
||||
.filter(|_id| !id.eq(*_id))
|
||||
.take(RECENTLY_VIEWED_MAX - 1)
|
||||
.collect::<VecDeque<_>>();
|
||||
recents.push_front(id);
|
||||
doc! { format!("recents.{resource_type}"): to_bson(&recents)? }
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
(user.recent_deployments, id, "recent_deployments")
|
||||
None => {
|
||||
doc! { format!("recents.{resource_type}"): [id] }
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
(user.recent_builds, id, "recent_builds")
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
(user.recent_repos, id, "recent_repos")
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
(user.recent_procedures, id, "recent_procedures")
|
||||
}
|
||||
_ => return Ok(PushRecentlyViewedResponse {}),
|
||||
};
|
||||
|
||||
let mut recents = recents
|
||||
.into_iter()
|
||||
.filter(|_id| !id.eq(_id))
|
||||
.take(RECENTLY_VIEWED_MAX - 1)
|
||||
.collect::<VecDeque<_>>();
|
||||
recents.push_front(id);
|
||||
let update = doc! { field: to_bson(&recents)? };
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().await.users,
|
||||
&user.id,
|
||||
@@ -124,7 +109,9 @@ impl Resolve<PushRecentlyViewed, User> for State {
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to update {field}"))?;
|
||||
.with_context(|| {
|
||||
format!("failed to update recents.{resource_type}")
|
||||
})?;
|
||||
|
||||
Ok(PushRecentlyViewedResponse {})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{build::Build, permission::PermissionLevel, user::User},
|
||||
entities::{
|
||||
build::{Build, PartialBuildConfig},
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
user::User,
|
||||
NoData,
|
||||
},
|
||||
};
|
||||
use octorust::types::{
|
||||
ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource, state::State};
|
||||
use crate::{
|
||||
config::core_config,
|
||||
resource,
|
||||
state::{github_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<CreateBuild, User> for State {
|
||||
#[instrument(name = "CreateBuild", skip(self, user))]
|
||||
@@ -56,3 +70,181 @@ impl Resolve<UpdateBuild, User> for State {
|
||||
resource::update::<Build>(&id, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CreateBuildWebhook, User> for State {
|
||||
#[instrument(name = "CreateBuildWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateBuildWebhook { build }: CreateBuildWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateBuildWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&build,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if build.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't create webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = build.config.repo.split('/');
|
||||
let owner = split.next().context("Build repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo =
|
||||
split.next().context("Build repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
// First make sure the webhook isn't already created (inactive ones are ignored)
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
webhook_secret,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = format!("{host}/listener/github/build/{}", build.id);
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// Now good to create the webhook
|
||||
let request = ReposCreateWebhookRequest {
|
||||
active: Some(true),
|
||||
config: Some(ReposCreateWebhookRequestConfig {
|
||||
url,
|
||||
secret: webhook_secret.to_string(),
|
||||
content_type: String::from("json"),
|
||||
insecure_ssl: None,
|
||||
digest: Default::default(),
|
||||
token: Default::default(),
|
||||
}),
|
||||
events: vec![String::from("push")],
|
||||
name: String::from("web"),
|
||||
};
|
||||
github_repos
|
||||
.create_webhook(owner, repo, &request)
|
||||
.await
|
||||
.context("failed to create webhook")?;
|
||||
|
||||
if !build.config.webhook_enabled {
|
||||
self
|
||||
.resolve(
|
||||
UpdateBuild {
|
||||
id: build.id,
|
||||
config: PartialBuildConfig {
|
||||
webhook_enabled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.context("failed to update build to enable webhook")?;
|
||||
}
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteBuildWebhook, User> for State {
|
||||
#[instrument(name = "DeleteBuildWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteBuildWebhook { build }: DeleteBuildWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteBuildWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&build,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if build.config.git_provider != "github.com" {
|
||||
return Err(anyhow!(
|
||||
"Can only manage github.com repo webhooks"
|
||||
));
|
||||
}
|
||||
|
||||
if build.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't delete webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = build.config.repo.split('/');
|
||||
let owner = split.next().context("Build repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo =
|
||||
split.next().context("Build repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = format!("{host}/listener/github/build/{}", build.id);
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
github_repos
|
||||
.delete_webhook(owner, repo, webhook.id)
|
||||
.await
|
||||
.context("failed to delete webhook")?;
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// No webhook to delete, all good
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ pub enum WriteRequest {
|
||||
|
||||
// ==== PERMISSIONS ====
|
||||
UpdateUserBasePermissions(UpdateUserBasePermissions),
|
||||
UpdatePermissionOnResourceType(UpdatePermissionOnResourceType),
|
||||
UpdatePermissionOnTarget(UpdatePermissionOnTarget),
|
||||
|
||||
// ==== DESCRIPTION ====
|
||||
@@ -75,6 +76,8 @@ pub enum WriteRequest {
|
||||
CopyBuild(CopyBuild),
|
||||
DeleteBuild(DeleteBuild),
|
||||
UpdateBuild(UpdateBuild),
|
||||
CreateBuildWebhook(CreateBuildWebhook),
|
||||
DeleteBuildWebhook(DeleteBuildWebhook),
|
||||
|
||||
// ==== BUILDER ====
|
||||
CreateBuilder(CreateBuilder),
|
||||
@@ -93,6 +96,8 @@ pub enum WriteRequest {
|
||||
CopyRepo(CopyRepo),
|
||||
DeleteRepo(DeleteRepo),
|
||||
UpdateRepo(UpdateRepo),
|
||||
CreateRepoWebhook(CreateRepoWebhook),
|
||||
DeleteRepoWebhook(DeleteRepoWebhook),
|
||||
|
||||
// ==== ALERTER ====
|
||||
CreateAlerter(CreateAlerter),
|
||||
@@ -112,6 +117,8 @@ pub enum WriteRequest {
|
||||
DeleteResourceSync(DeleteResourceSync),
|
||||
UpdateResourceSync(UpdateResourceSync),
|
||||
RefreshResourceSyncPending(RefreshResourceSyncPending),
|
||||
CreateSyncWebhook(CreateSyncWebhook),
|
||||
DeleteSyncWebhook(DeleteSyncWebhook),
|
||||
|
||||
// ==== TAG ====
|
||||
CreateTag(CreateTag),
|
||||
|
||||
@@ -3,8 +3,10 @@ use std::str::FromStr;
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
UpdatePermissionOnTarget, UpdatePermissionOnTargetResponse,
|
||||
UpdateUserBasePermissions, UpdateUserBasePermissionsResponse,
|
||||
UpdatePermissionOnResourceType,
|
||||
UpdatePermissionOnResourceTypeResponse, UpdatePermissionOnTarget,
|
||||
UpdatePermissionOnTargetResponse, UpdateUserBasePermissions,
|
||||
UpdateUserBasePermissionsResponse,
|
||||
},
|
||||
entities::{
|
||||
permission::{UserTarget, UserTargetVariant},
|
||||
@@ -41,6 +43,7 @@ impl Resolve<UpdateUserBasePermissions, User> for State {
|
||||
if !admin.admin {
|
||||
return Err(anyhow!("this method is admin only"));
|
||||
}
|
||||
|
||||
let user = find_one_by_id(&db_client().await.users, &user_id)
|
||||
.await
|
||||
.context("failed to query mongo for user")?
|
||||
@@ -73,6 +76,73 @@ impl Resolve<UpdateUserBasePermissions, User> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<UpdatePermissionOnResourceType, User> for State {
|
||||
#[instrument(
|
||||
name = "UpdatePermissionOnResourceType",
|
||||
skip(self, admin)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdatePermissionOnResourceType {
|
||||
user_target,
|
||||
resource_type,
|
||||
permission,
|
||||
}: UpdatePermissionOnResourceType,
|
||||
admin: User,
|
||||
) -> anyhow::Result<UpdatePermissionOnResourceTypeResponse> {
|
||||
if !admin.admin {
|
||||
return Err(anyhow!("this method is admin only"));
|
||||
}
|
||||
|
||||
// Some extra checks if user target is an actual User
|
||||
if let UserTarget::User(user_id) = &user_target {
|
||||
let user = get_user(user_id).await?;
|
||||
if user.admin {
|
||||
return Err(anyhow!(
|
||||
"cannot use this method to update other admins permissions"
|
||||
));
|
||||
}
|
||||
if !user.enabled {
|
||||
return Err(anyhow!("user not enabled"));
|
||||
}
|
||||
}
|
||||
|
||||
let (user_target_variant, user_target_id) =
|
||||
extract_user_target_with_validation(&user_target).await?;
|
||||
|
||||
let id = ObjectId::from_str(&user_target_id)
|
||||
.context("id is not ObjectId")?;
|
||||
let field = format!("all.{resource_type}");
|
||||
let filter = doc! { "_id": id };
|
||||
let update = doc! { "$set": { &field: permission.as_ref() } };
|
||||
|
||||
match user_target_variant {
|
||||
UserTargetVariant::User => {
|
||||
db_client()
|
||||
.await
|
||||
.users
|
||||
.update_one(filter, update)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to set {field}: {permission} on db")
|
||||
})?;
|
||||
}
|
||||
UserTargetVariant::UserGroup => {
|
||||
db_client()
|
||||
.await
|
||||
.user_groups
|
||||
.update_one(filter, update)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to set {field}: {permission} on db")
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(UpdatePermissionOnResourceTypeResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<UpdatePermissionOnTarget, User> for State {
|
||||
#[instrument(name = "UpdatePermissionOnTarget", skip(self, admin))]
|
||||
async fn resolve(
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{permission::PermissionLevel, repo::Repo, user::User},
|
||||
entities::{
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
repo::{PartialRepoConfig, Repo},
|
||||
user::User,
|
||||
NoData,
|
||||
},
|
||||
};
|
||||
use octorust::types::{
|
||||
ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource, state::State};
|
||||
use crate::{
|
||||
config::core_config,
|
||||
resource,
|
||||
state::{github_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<CreateRepo, User> for State {
|
||||
#[instrument(name = "CreateRepo", skip(self, user))]
|
||||
@@ -56,3 +70,196 @@ impl Resolve<UpdateRepo, User> for State {
|
||||
resource::update::<Repo>(&id, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CreateRepoWebhook, User> for State {
|
||||
#[instrument(name = "CreateRepoWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateRepoWebhook { repo, action }: CreateRepoWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateRepoWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
&repo,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if repo.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't create webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = repo.config.repo.split('/');
|
||||
let owner = split.next().context("Repo repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo_name =
|
||||
split.next().context("Repo repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
// First make sure the webhook isn't already created (inactive ones are ignored)
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo_name)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
webhook_secret,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = match action {
|
||||
RepoWebhookAction::Clone => {
|
||||
format!("{host}/listener/github/repo/{}/clone", repo.id)
|
||||
}
|
||||
RepoWebhookAction::Pull => {
|
||||
format!("{host}/listener/github/repo/{}/pull", repo.id)
|
||||
}
|
||||
};
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// Now good to create the webhook
|
||||
let request = ReposCreateWebhookRequest {
|
||||
active: Some(true),
|
||||
config: Some(ReposCreateWebhookRequestConfig {
|
||||
url,
|
||||
secret: webhook_secret.to_string(),
|
||||
content_type: String::from("json"),
|
||||
insecure_ssl: None,
|
||||
digest: Default::default(),
|
||||
token: Default::default(),
|
||||
}),
|
||||
events: vec![String::from("push")],
|
||||
name: String::from("web"),
|
||||
};
|
||||
github_repos
|
||||
.create_webhook(owner, repo_name, &request)
|
||||
.await
|
||||
.context("failed to create webhook")?;
|
||||
|
||||
if !repo.config.webhook_enabled {
|
||||
self
|
||||
.resolve(
|
||||
UpdateRepo {
|
||||
id: repo.id,
|
||||
config: PartialRepoConfig {
|
||||
webhook_enabled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.context("failed to update repo to enable webhook")?;
|
||||
}
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteRepoWebhook, User> for State {
|
||||
#[instrument(name = "DeleteRepoWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteRepoWebhook { repo, action }: DeleteRepoWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteRepoWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
&repo,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if repo.config.git_provider != "github.com" {
|
||||
return Err(anyhow!(
|
||||
"Can only manage github.com repo webhooks"
|
||||
));
|
||||
}
|
||||
|
||||
if repo.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't create webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = repo.config.repo.split('/');
|
||||
let owner = split.next().context("Repo repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo_name =
|
||||
split.next().context("Repo repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
// First make sure the webhook isn't already created (inactive ones are ignored)
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo_name)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = match action {
|
||||
RepoWebhookAction::Clone => {
|
||||
format!("{host}/listener/github/repo/{}/clone", repo.id)
|
||||
}
|
||||
RepoWebhookAction::Pull => {
|
||||
format!("{host}/listener/github/repo/{}/pull", repo.id)
|
||||
}
|
||||
};
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
github_repos
|
||||
.delete_webhook(owner, repo_name, webhook.id)
|
||||
.await
|
||||
.context("failed to delete webhook")?;
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// No webhook to delete, all good
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +51,8 @@ impl Resolve<CreateServiceUser, User> for State {
|
||||
create_server_permissions: false,
|
||||
create_build_permissions: false,
|
||||
last_update_view: 0,
|
||||
recent_servers: Vec::new(),
|
||||
recent_deployments: Vec::new(),
|
||||
recent_builds: Vec::new(),
|
||||
recent_repos: Vec::new(),
|
||||
recent_procedures: Vec::new(),
|
||||
recents: Default::default(),
|
||||
all: Default::default(),
|
||||
updated_at: monitor_timestamp(),
|
||||
};
|
||||
user.id = db_client()
|
||||
|
||||
+208
-11
@@ -8,6 +8,7 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
config::core::CoreConfig,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -15,21 +16,26 @@ use monitor_client::{
|
||||
server::{stats::SeverityLevel, Server},
|
||||
server_template::ServerTemplate,
|
||||
sync::{
|
||||
PendingSyncUpdates, PendingSyncUpdatesData,
|
||||
PendingSyncUpdatesDataErr, PendingSyncUpdatesDataOk,
|
||||
ResourceSync,
|
||||
PartialResourceSyncConfig, PendingSyncUpdates,
|
||||
PendingSyncUpdatesData, PendingSyncUpdatesDataErr,
|
||||
PendingSyncUpdatesDataOk, ResourceSync,
|
||||
},
|
||||
update::ResourceTarget,
|
||||
user::User,
|
||||
NoData,
|
||||
},
|
||||
};
|
||||
use mungos::{
|
||||
by_id::update_one_by_id,
|
||||
mongodb::bson::{doc, to_document},
|
||||
};
|
||||
use octorust::types::{
|
||||
ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
alert::send_alerts,
|
||||
query::get_id_to_tags,
|
||||
@@ -39,7 +45,7 @@ use crate::{
|
||||
},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
state::{db_client, github_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<CreateResourceSync, User> for State {
|
||||
@@ -262,13 +268,11 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
let Some(existing) = db_client()
|
||||
.await
|
||||
.alerts
|
||||
.find_one(
|
||||
doc! {
|
||||
"resolved": false,
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": &id,
|
||||
},
|
||||
)
|
||||
.find_one(doc! {
|
||||
"resolved": false,
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": &id,
|
||||
})
|
||||
.await
|
||||
.context("failed to query db for alert")
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
@@ -322,3 +326,196 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
crate::resource::get::<ResourceSync>(&sync.id).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CreateSyncWebhook, User> for State {
|
||||
#[instrument(name = "CreateSyncWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateSyncWebhook { sync, action }: CreateSyncWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateSyncWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&sync,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if sync.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't create webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = sync.config.repo.split('/');
|
||||
let owner = split.next().context("Sync repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo =
|
||||
split.next().context("Repo repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
// First make sure the webhook isn't already created (inactive ones are ignored)
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
webhook_secret,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = match action {
|
||||
SyncWebhookAction::Refresh => {
|
||||
format!("{host}/listener/github/sync/{}/refresh", sync.id)
|
||||
}
|
||||
SyncWebhookAction::Sync => {
|
||||
format!("{host}/listener/github/sync/{}/sync", sync.id)
|
||||
}
|
||||
};
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// Now good to create the webhook
|
||||
let request = ReposCreateWebhookRequest {
|
||||
active: Some(true),
|
||||
config: Some(ReposCreateWebhookRequestConfig {
|
||||
url,
|
||||
secret: webhook_secret.to_string(),
|
||||
content_type: String::from("json"),
|
||||
insecure_ssl: None,
|
||||
digest: Default::default(),
|
||||
token: Default::default(),
|
||||
}),
|
||||
events: vec![String::from("push")],
|
||||
name: String::from("web"),
|
||||
};
|
||||
github_repos
|
||||
.create_webhook(owner, repo, &request)
|
||||
.await
|
||||
.context("failed to create webhook")?;
|
||||
|
||||
if !sync.config.webhook_enabled {
|
||||
self
|
||||
.resolve(
|
||||
UpdateResourceSync {
|
||||
id: sync.id,
|
||||
config: PartialResourceSyncConfig {
|
||||
webhook_enabled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
user,
|
||||
)
|
||||
.await
|
||||
.context("failed to update sync to enable webhook")?;
|
||||
}
|
||||
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteSyncWebhook, User> for State {
|
||||
#[instrument(name = "DeleteSyncWebhook", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteSyncWebhook { sync, action }: DeleteSyncWebhook,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteSyncWebhookResponse> {
|
||||
let Some(github) = github_client() else {
|
||||
return Err(anyhow!(
|
||||
"github_webhook_app is not configured in core config toml"
|
||||
));
|
||||
};
|
||||
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&sync,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if sync.config.git_provider != "github.com" {
|
||||
return Err(anyhow!(
|
||||
"Can only manage github.com repo webhooks"
|
||||
));
|
||||
}
|
||||
|
||||
if sync.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"No repo configured, can't create webhook"
|
||||
));
|
||||
}
|
||||
|
||||
let mut split = sync.config.repo.split('/');
|
||||
let owner = split.next().context("Sync repo has no owner")?;
|
||||
|
||||
let Some(github) = github.get(owner) else {
|
||||
return Err(anyhow!(
|
||||
"Cannot manage repo webhooks under owner {owner}"
|
||||
));
|
||||
};
|
||||
|
||||
let repo =
|
||||
split.next().context("Sync repo has no repo after the /")?;
|
||||
|
||||
let github_repos = github.repos();
|
||||
|
||||
// First make sure the webhook isn't already created (inactive ones are ignored)
|
||||
let webhooks = github_repos
|
||||
.list_all_webhooks(owner, repo)
|
||||
.await
|
||||
.context("failed to list all webhooks on repo")?
|
||||
.body;
|
||||
|
||||
let CoreConfig {
|
||||
host,
|
||||
webhook_base_url,
|
||||
..
|
||||
} = core_config();
|
||||
|
||||
let host = webhook_base_url.as_ref().unwrap_or(host);
|
||||
let url = match action {
|
||||
SyncWebhookAction::Refresh => {
|
||||
format!("{host}/listener/github/sync/{}/refresh", sync.id)
|
||||
}
|
||||
SyncWebhookAction::Sync => {
|
||||
format!("{host}/listener/github/sync/{}/sync", sync.id)
|
||||
}
|
||||
};
|
||||
|
||||
for webhook in webhooks {
|
||||
if webhook.active && webhook.config.url == url {
|
||||
github_repos
|
||||
.delete_webhook(owner, repo, webhook.id)
|
||||
.await
|
||||
.context("failed to delete webhook")?;
|
||||
return Ok(NoData {});
|
||||
}
|
||||
}
|
||||
|
||||
// No webhook to delete, all good
|
||||
Ok(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ impl Resolve<CreateUserGroup, User> for State {
|
||||
let user_group = UserGroup {
|
||||
id: Default::default(),
|
||||
users: Default::default(),
|
||||
all: Default::default(),
|
||||
updated_at: monitor_timestamp(),
|
||||
name,
|
||||
};
|
||||
@@ -229,7 +230,7 @@ impl Resolve<SetUsersInUserGroup, User> for State {
|
||||
db.user_groups
|
||||
.update_one(filter.clone(), doc! { "$set": { "users": users } })
|
||||
.await
|
||||
.context("failed to add user to group on db")?;
|
||||
.context("failed to set users on user group")?;
|
||||
db.user_groups
|
||||
.find_one(filter)
|
||||
.await
|
||||
|
||||
@@ -87,11 +87,8 @@ async fn callback(
|
||||
create_build_permissions: no_users_exist,
|
||||
updated_at: ts,
|
||||
last_update_view: 0,
|
||||
recent_servers: Vec::new(),
|
||||
recent_deployments: Vec::new(),
|
||||
recent_builds: Vec::new(),
|
||||
recent_repos: Vec::new(),
|
||||
recent_procedures: Vec::new(),
|
||||
recents: Default::default(),
|
||||
all: Default::default(),
|
||||
config: UserConfig::Github {
|
||||
github_id,
|
||||
avatar: github_user.avatar_url,
|
||||
|
||||
@@ -102,11 +102,8 @@ async fn callback(
|
||||
create_build_permissions: no_users_exist,
|
||||
updated_at: ts,
|
||||
last_update_view: 0,
|
||||
recent_servers: Vec::new(),
|
||||
recent_deployments: Vec::new(),
|
||||
recent_builds: Vec::new(),
|
||||
recent_repos: Vec::new(),
|
||||
recent_procedures: Vec::new(),
|
||||
recents: Default::default(),
|
||||
all: Default::default(),
|
||||
config: UserConfig::Google {
|
||||
google_id,
|
||||
avatar: google_user.picture,
|
||||
|
||||
@@ -62,11 +62,8 @@ impl Resolve<CreateLocalUser, HeaderMap> for State {
|
||||
create_build_permissions: no_users_exist,
|
||||
updated_at: ts,
|
||||
last_update_view: 0,
|
||||
recent_servers: Vec::new(),
|
||||
recent_deployments: Vec::new(),
|
||||
recent_builds: Vec::new(),
|
||||
recent_repos: Vec::new(),
|
||||
recent_procedures: Vec::new(),
|
||||
recents: Default::default(),
|
||||
all: Default::default(),
|
||||
config: UserConfig::Local { password },
|
||||
};
|
||||
|
||||
|
||||
@@ -233,32 +233,30 @@ pub enum HetznerActionStatus {
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum HetznerServerType {
|
||||
// Shared
|
||||
#[serde(rename = "cx11")]
|
||||
SharedIntel1Core2Ram20Disk,
|
||||
#[serde(rename = "cpx11")]
|
||||
SharedAmd2Core2Ram40Disk,
|
||||
#[serde(rename = "cax11")]
|
||||
SharedArm2Core4Ram40Disk,
|
||||
#[serde(rename = "cx21")]
|
||||
#[serde(rename = "cx22")]
|
||||
SharedIntel2Core4Ram40Disk,
|
||||
#[serde(rename = "cpx21")]
|
||||
SharedAmd3Core4Ram80Disk,
|
||||
#[serde(rename = "cax21")]
|
||||
SharedArm4Core8Ram80Disk,
|
||||
#[serde(rename = "cx31")]
|
||||
SharedIntel2Core8Ram80Disk,
|
||||
#[serde(rename = "cx32")]
|
||||
SharedIntel4Core8Ram80Disk,
|
||||
#[serde(rename = "cpx31")]
|
||||
SharedAmd4Core8Ram160Disk,
|
||||
#[serde(rename = "cax31")]
|
||||
SharedArm8Core16Ram160Disk,
|
||||
#[serde(rename = "cx41")]
|
||||
SharedIntel4Core16Ram160Disk,
|
||||
#[serde(rename = "cx42")]
|
||||
SharedIntel8Core16Ram160Disk,
|
||||
#[serde(rename = "cpx41")]
|
||||
SharedAmd8Core16Ram240Disk,
|
||||
#[serde(rename = "cax41")]
|
||||
SharedArm16Core32Ram320Disk,
|
||||
#[serde(rename = "cx51")]
|
||||
SharedIntel8Core32Ram240Disk,
|
||||
#[serde(rename = "cx52")]
|
||||
SharedIntel16Core32Ram320Disk,
|
||||
#[serde(rename = "cpx51")]
|
||||
SharedAmd16Core32Ram360Disk,
|
||||
// Dedicated
|
||||
|
||||
@@ -218,9 +218,6 @@ fn hetzner_server_type(
|
||||
server_type: HetznerServerType,
|
||||
) -> common::HetznerServerType {
|
||||
match server_type {
|
||||
HetznerServerType::SharedIntel1Core2Ram20Disk => {
|
||||
common::HetznerServerType::SharedIntel1Core2Ram20Disk
|
||||
}
|
||||
HetznerServerType::SharedAmd2Core2Ram40Disk => {
|
||||
common::HetznerServerType::SharedAmd2Core2Ram40Disk
|
||||
}
|
||||
@@ -236,8 +233,8 @@ fn hetzner_server_type(
|
||||
HetznerServerType::SharedArm4Core8Ram80Disk => {
|
||||
common::HetznerServerType::SharedArm4Core8Ram80Disk
|
||||
}
|
||||
HetznerServerType::SharedIntel2Core8Ram80Disk => {
|
||||
common::HetznerServerType::SharedIntel2Core8Ram80Disk
|
||||
HetznerServerType::SharedIntel4Core8Ram80Disk => {
|
||||
common::HetznerServerType::SharedIntel4Core8Ram80Disk
|
||||
}
|
||||
HetznerServerType::SharedAmd4Core8Ram160Disk => {
|
||||
common::HetznerServerType::SharedAmd4Core8Ram160Disk
|
||||
@@ -245,8 +242,8 @@ fn hetzner_server_type(
|
||||
HetznerServerType::SharedArm8Core16Ram160Disk => {
|
||||
common::HetznerServerType::SharedArm8Core16Ram160Disk
|
||||
}
|
||||
HetznerServerType::SharedIntel4Core16Ram160Disk => {
|
||||
common::HetznerServerType::SharedIntel4Core16Ram160Disk
|
||||
HetznerServerType::SharedIntel8Core16Ram160Disk => {
|
||||
common::HetznerServerType::SharedIntel8Core16Ram160Disk
|
||||
}
|
||||
HetznerServerType::SharedAmd8Core16Ram240Disk => {
|
||||
common::HetznerServerType::SharedAmd8Core16Ram240Disk
|
||||
@@ -254,8 +251,8 @@ fn hetzner_server_type(
|
||||
HetznerServerType::SharedArm16Core32Ram320Disk => {
|
||||
common::HetznerServerType::SharedArm16Core32Ram320Disk
|
||||
}
|
||||
HetznerServerType::SharedIntel8Core32Ram240Disk => {
|
||||
common::HetznerServerType::SharedIntel8Core32Ram240Disk
|
||||
HetznerServerType::SharedIntel16Core32Ram320Disk => {
|
||||
common::HetznerServerType::SharedIntel16Core32Ram320Disk
|
||||
}
|
||||
HetznerServerType::SharedAmd16Core32Ram360Disk => {
|
||||
common::HetznerServerType::SharedAmd16Core32Ram360Disk
|
||||
|
||||
+48
-18
@@ -4,8 +4,9 @@ use anyhow::Context;
|
||||
use merge_config_files::parse_config_file;
|
||||
use monitor_client::entities::{
|
||||
config::core::{
|
||||
AwsCredentials, CoreConfig, Env, HetznerCredentials, MongoConfig,
|
||||
OauthCredentials,
|
||||
AwsCredentials, CoreConfig, Env, GithubWebhookAppConfig,
|
||||
GithubWebhookAppInstallationConfig, HetznerCredentials,
|
||||
MongoConfig, OauthCredentials,
|
||||
},
|
||||
logger::LogConfig,
|
||||
};
|
||||
@@ -36,15 +37,40 @@ pub fn frontend_path() -> &'static String {
|
||||
pub fn core_config() -> &'static CoreConfig {
|
||||
static CORE_CONFIG: OnceLock<CoreConfig> = OnceLock::new();
|
||||
CORE_CONFIG.get_or_init(|| {
|
||||
let env: Env = envy::from_env()
|
||||
.context("failed to parse core Env")
|
||||
.unwrap();
|
||||
let env: Env = match envy::from_env()
|
||||
.context("failed to parse core Env") {
|
||||
Ok(env) => env,
|
||||
Err(e) => {
|
||||
panic!("{e:#?}");
|
||||
}
|
||||
};
|
||||
let config_path = &env.monitor_config_path;
|
||||
let config =
|
||||
parse_config_file::<CoreConfig>(config_path.as_str())
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("failed at parsing config at {config_path} | {e:#}")
|
||||
});
|
||||
let installations = match (env.monitor_github_webhook_app_installations_ids, env.monitor_github_webhook_app_installations_namespaces) {
|
||||
(Some(ids), Some(namespaces)) => {
|
||||
if ids.len() != namespaces.len() {
|
||||
panic!("MONITOR_GITHUB_WEBHOOK_APP_INSTALLATIONS_IDS length and MONITOR_GITHUB_WEBHOOK_APP_INSTALLATIONS_NAMESPACES length mismatch. Got {ids:?} and {namespaces:?}")
|
||||
}
|
||||
ids
|
||||
.into_iter()
|
||||
.zip(namespaces)
|
||||
.map(|(id, namespace)| GithubWebhookAppInstallationConfig {
|
||||
id,
|
||||
namespace
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
panic!("Got only one of MONITOR_GITHUB_WEBHOOK_APP_INSTALLATIONS_IDS or MONITOR_GITHUB_WEBHOOK_APP_INSTALLATIONS_NAMESPACES, both MUST be provided");
|
||||
}
|
||||
(None, None) => {
|
||||
config.github_webhook_app.installations
|
||||
}
|
||||
};
|
||||
// recreating CoreConfig here makes sure we apply all env overrides.
|
||||
CoreConfig {
|
||||
title: env.monitor_title.unwrap_or(config.title),
|
||||
@@ -69,17 +95,12 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
keep_alerts_for_days: env
|
||||
.monitor_keep_alerts_for_days
|
||||
.unwrap_or(config.keep_alerts_for_days),
|
||||
github_webhook_secret: env
|
||||
.monitor_github_webhook_secret
|
||||
.unwrap_or(config.github_webhook_secret),
|
||||
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),
|
||||
webhook_secret: env
|
||||
.monitor_webhook_secret
|
||||
.unwrap_or(config.webhook_secret),
|
||||
webhook_base_url: env
|
||||
.monitor_webhook_base_url
|
||||
.or(config.webhook_base_url),
|
||||
transparent_mode: env
|
||||
.monitor_transparent_mode
|
||||
.unwrap_or(config.transparent_mode),
|
||||
@@ -109,6 +130,15 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
.monitor_github_oauth_secret
|
||||
.unwrap_or(config.github_oauth.secret),
|
||||
},
|
||||
github_webhook_app: GithubWebhookAppConfig {
|
||||
app_id: env
|
||||
.monitor_github_webhook_app_app_id
|
||||
.unwrap_or(config.github_webhook_app.app_id),
|
||||
pk_path: env
|
||||
.monitor_github_webhook_app_pk_path
|
||||
.unwrap_or(config.github_webhook_app.pk_path),
|
||||
installations,
|
||||
},
|
||||
aws: AwsCredentials {
|
||||
access_key_id: env
|
||||
.monitor_aws_access_key_id
|
||||
@@ -155,8 +185,8 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
|
||||
// These can't be overridden on env
|
||||
secrets: config.secrets,
|
||||
github_accounts: config.github_accounts,
|
||||
docker_accounts: config.docker_accounts,
|
||||
git_providers: config.git_providers,
|
||||
docker_registries: config.docker_registries,
|
||||
aws_ecr_registries: config.aws_ecr_registries,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -19,20 +19,18 @@ pub async fn send_alerts(alerts: &[Alert]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let alerters = match find_collect(
|
||||
let Ok(alerters) = find_collect(
|
||||
&db_client().await.alerters,
|
||||
doc! { "config.enabled": true },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(alerters) => alerters,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"ERROR sending alerts | failed to get alerters from db | {e:#}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
.inspect_err(|e| {
|
||||
error!(
|
||||
"ERROR sending alerts | failed to get alerters from db | {e:#}"
|
||||
)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let handles =
|
||||
@@ -337,7 +335,7 @@ async fn send_slack_alert(
|
||||
}
|
||||
AlertData::ResourceSyncPendingUpdates { id, name } => {
|
||||
let text =
|
||||
format!("{level} | There are pending resource sync updates");
|
||||
format!("{level} | Pending resource sync updates on {name}");
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
@@ -350,29 +348,12 @@ async fn send_slack_alert(
|
||||
];
|
||||
(text, blocks.into())
|
||||
}
|
||||
AlertData::BuildFailed {
|
||||
id,
|
||||
name,
|
||||
version,
|
||||
err,
|
||||
} => {
|
||||
AlertData::BuildFailed { id, name, version } => {
|
||||
let text = format!("{level} | Build {name} has failed");
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|log| {
|
||||
let stdout = (!log.stdout.is_empty())
|
||||
.then(|| format!("\nstdout: {}", log.stdout))
|
||||
.unwrap_or_default();
|
||||
let stderr = (!log.stderr.is_empty())
|
||||
.then(|| format!("\nstderr: {}", log.stderr))
|
||||
.unwrap_or_default();
|
||||
format!("\nfailed at stage: {}{stdout}{stderr}", log.stage)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let blocks = vec![
|
||||
Block::header(text.clone()),
|
||||
Block::section(format!(
|
||||
"build id: *{id}*\nbuild name: *{name}*\nversion: v{version}{err}",
|
||||
"build id: *{id}*\nbuild name: *{name}*\nversion: v{version}",
|
||||
)),
|
||||
Block::section(resource_link(ResourceTargetVariant::Build, id))
|
||||
];
|
||||
|
||||
@@ -103,14 +103,12 @@ pub async fn create_permission<T>(
|
||||
if let Err(e) = db_client()
|
||||
.await
|
||||
.permissions
|
||||
.insert_one(
|
||||
Permission {
|
||||
id: Default::default(),
|
||||
user_target: UserTarget::User(user.id.clone()),
|
||||
resource_target: target.clone(),
|
||||
level,
|
||||
},
|
||||
)
|
||||
.insert_one(Permission {
|
||||
id: Default::default(),
|
||||
user_target: UserTarget::User(user.id.clone()),
|
||||
resource_target: target.clone(),
|
||||
level,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!("failed to create permission for {target:?} | {e:#}");
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{anyhow, Context, Ok};
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{bold, colored, format_serror, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
procedure::Procedure, update::Update, user::procedure_user,
|
||||
procedure::Procedure,
|
||||
update::{Log, Update},
|
||||
user::procedure_user,
|
||||
},
|
||||
};
|
||||
use mungos::by_id::find_one_by_id;
|
||||
use resolver_api::Resolve;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{api::execute::ExecuteRequest, state::State};
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
use super::update::{init_execution_update, update_update};
|
||||
|
||||
@@ -49,8 +55,7 @@ pub async fn execute_procedure(
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"{}: failed stage '{}' execution after {:?}",
|
||||
colored("ERROR", Color::Red),
|
||||
"failed stage '{}' execution after {:?}",
|
||||
bold(&stage.name),
|
||||
timer.elapsed(),
|
||||
)
|
||||
@@ -130,10 +135,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::RunProcedure(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunProcedure")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunProcedure"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::RunBuild(req) => {
|
||||
let req = ExecuteRequest::RunBuild(req);
|
||||
@@ -141,10 +151,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::RunBuild(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunBuild")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunBuild"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::Deploy(req) => {
|
||||
let req = ExecuteRequest::Deploy(req);
|
||||
@@ -152,10 +167,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::Deploy(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at Deploy")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at Deploy"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::StartContainer(req) => {
|
||||
let req = ExecuteRequest::StartContainer(req);
|
||||
@@ -163,10 +183,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::StartContainer(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StartContainer")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StartContainer"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::StopContainer(req) => {
|
||||
let req = ExecuteRequest::StopContainer(req);
|
||||
@@ -174,10 +199,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::StopContainer(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StopContainer")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StopContainer"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::StopAllContainers(req) => {
|
||||
let req = ExecuteRequest::StopAllContainers(req);
|
||||
@@ -185,10 +215,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::StopAllContainers(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StopAllContainers")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at StopAllContainers"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::RemoveContainer(req) => {
|
||||
let req = ExecuteRequest::RemoveContainer(req);
|
||||
@@ -196,10 +231,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::RemoveContainer(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RemoveContainer")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RemoveContainer"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::CloneRepo(req) => {
|
||||
let req = ExecuteRequest::CloneRepo(req);
|
||||
@@ -207,10 +247,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::CloneRepo(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at CloneRepo")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at CloneRepo"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::PullRepo(req) => {
|
||||
let req = ExecuteRequest::PullRepo(req);
|
||||
@@ -218,10 +263,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::PullRepo(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PullRepo")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PullRepo"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::PruneNetworks(req) => {
|
||||
let req = ExecuteRequest::PruneNetworks(req);
|
||||
@@ -229,10 +279,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::PruneNetworks(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneNetworks")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneNetworks"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::PruneImages(req) => {
|
||||
let req = ExecuteRequest::PruneImages(req);
|
||||
@@ -240,10 +295,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::PruneImages(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneImages")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneImages"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::PruneContainers(req) => {
|
||||
let req = ExecuteRequest::PruneContainers(req);
|
||||
@@ -251,10 +311,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::PruneContainers(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneContainers")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at PruneContainers"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::RunSync(req) => {
|
||||
let req = ExecuteRequest::RunSync(req);
|
||||
@@ -262,10 +327,15 @@ async fn execute_execution(
|
||||
let ExecuteRequest::RunSync(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunSync")?
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("failed at RunSync"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::Sleep(req) => {
|
||||
tokio::time::sleep(Duration::from_millis(
|
||||
@@ -289,6 +359,30 @@ async fn execute_execution(
|
||||
}
|
||||
}
|
||||
|
||||
/// If the call to .resolve returns Err, the update may not be closed.
|
||||
/// This will ensure it is closed with error log attached.
|
||||
async fn handle_resolve_result(
|
||||
res: anyhow::Result<Update>,
|
||||
update_id: &str,
|
||||
) -> anyhow::Result<Update> {
|
||||
match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
let log =
|
||||
Log::error("execution error", format_serror(&e.into()));
|
||||
let mut update =
|
||||
find_one_by_id(&db_client().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.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ASSUMES FIRST LOG IS ALREADY CREATED
|
||||
#[instrument(level = "debug")]
|
||||
async fn add_line_to_update(update: &Mutex<Update>, line: &str) {
|
||||
|
||||
@@ -68,11 +68,9 @@ async fn prune_stats() -> anyhow::Result<()> {
|
||||
let res = db_client()
|
||||
.await
|
||||
.stats
|
||||
.delete_many(
|
||||
doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
},
|
||||
)
|
||||
.delete_many(doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
})
|
||||
.await?;
|
||||
info!("deleted {} stats from db", res.deleted_count);
|
||||
Ok(())
|
||||
@@ -88,11 +86,9 @@ async fn prune_alerts() -> anyhow::Result<()> {
|
||||
let res = db_client()
|
||||
.await
|
||||
.alerts
|
||||
.delete_many(
|
||||
doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
},
|
||||
)
|
||||
.delete_many(doc! {
|
||||
"ts": { "$lt": delete_before_ts }
|
||||
})
|
||||
.await?;
|
||||
info!("deleted {} alerts from db", res.deleted_count);
|
||||
Ok(())
|
||||
|
||||
+109
-36
@@ -11,11 +11,11 @@ use monitor_client::entities::{
|
||||
tag::Tag,
|
||||
update::{ResourceTargetVariant, Update},
|
||||
user::{admin_service_user, User},
|
||||
user_group::UserGroup,
|
||||
variable::Variable,
|
||||
Operation,
|
||||
};
|
||||
use mungos::{
|
||||
by_id::find_one_by_id,
|
||||
find::find_collect,
|
||||
mongodb::{
|
||||
bson::{doc, oid::ObjectId, Document},
|
||||
@@ -26,14 +26,18 @@ use mungos::{
|
||||
use crate::{config::core_config, resource, state::db_client};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user(user_id: &str) -> anyhow::Result<User> {
|
||||
if let Some(user) = admin_service_user(user_id) {
|
||||
// user: Id or username
|
||||
pub async fn get_user(user: &str) -> anyhow::Result<User> {
|
||||
if let Some(user) = admin_service_user(user) {
|
||||
return Ok(user);
|
||||
}
|
||||
find_one_by_id(&db_client().await.users, user_id)
|
||||
db_client()
|
||||
.await
|
||||
.users
|
||||
.find_one(id_or_username_filter(user))
|
||||
.await
|
||||
.context("failed to query mongo for user")?
|
||||
.with_context(|| format!("no user found with id {user_id}"))
|
||||
.with_context(|| format!("no user found with {user}"))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
@@ -120,10 +124,10 @@ pub async fn get_id_to_tags(
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user_user_group_ids(
|
||||
pub async fn get_user_user_groups(
|
||||
user_id: &str,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let res = find_collect(
|
||||
) -> anyhow::Result<Vec<UserGroup>> {
|
||||
find_collect(
|
||||
&db_client().await.user_groups,
|
||||
doc! {
|
||||
"users": user_id
|
||||
@@ -131,50 +135,84 @@ pub async fn get_user_user_group_ids(
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to query db for user groups")?
|
||||
.into_iter()
|
||||
.map(|ug| ug.id)
|
||||
.collect();
|
||||
.context("failed to query db for user groups")
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user_user_group_ids(
|
||||
user_id: &str,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let res = get_user_user_groups(user_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|ug| ug.id)
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns Vec of all queries on permissions that match against the user
|
||||
/// or any user groups that the user is a part of.
|
||||
/// Result used with Mongodb '$or'.
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn user_target_query(
|
||||
pub fn user_target_query(
|
||||
user_id: &str,
|
||||
user_groups: &[UserGroup],
|
||||
) -> anyhow::Result<Vec<Document>> {
|
||||
let mut user_target_query = vec![
|
||||
doc! { "user_target.type": "User", "user_target.id": user_id },
|
||||
];
|
||||
let user_groups = get_user_user_group_ids(user_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|ug_id| {
|
||||
doc! {
|
||||
"user_target.type": "UserGroup", "user_target.id": ug_id,
|
||||
}
|
||||
});
|
||||
let user_groups = user_groups.iter().map(|ug| {
|
||||
doc! {
|
||||
"user_target.type": "UserGroup", "user_target.id": &ug.id,
|
||||
}
|
||||
});
|
||||
user_target_query.extend(user_groups);
|
||||
Ok(user_target_query)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user_permission_on_resource(
|
||||
user_id: &str,
|
||||
user: &User,
|
||||
resource_variant: ResourceTargetVariant,
|
||||
resource_id: &str,
|
||||
) -> anyhow::Result<PermissionLevel> {
|
||||
let lowest_permission = if core_config().transparent_mode {
|
||||
if user.admin {
|
||||
return Ok(PermissionLevel::Write);
|
||||
}
|
||||
|
||||
// Start with base of Read or None
|
||||
let mut base = if core_config().transparent_mode {
|
||||
PermissionLevel::Read
|
||||
} else {
|
||||
PermissionLevel::None
|
||||
};
|
||||
|
||||
// Overlay users base on resource variant
|
||||
if let Some(level) = user.all.get(&resource_variant).cloned() {
|
||||
if level > base {
|
||||
base = level;
|
||||
}
|
||||
}
|
||||
if base == PermissionLevel::Write {
|
||||
// No reason to keep going if already Write at this point.
|
||||
return Ok(PermissionLevel::Write);
|
||||
}
|
||||
|
||||
// Overlay any user groups base on resource variant
|
||||
let groups = get_user_user_groups(&user.id).await?;
|
||||
for group in &groups {
|
||||
if let Some(level) = group.all.get(&resource_variant).cloned() {
|
||||
if level > base {
|
||||
base = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
if base == PermissionLevel::Write {
|
||||
// No reason to keep going if already Write at this point.
|
||||
return Ok(PermissionLevel::Write);
|
||||
}
|
||||
|
||||
// Overlay any specific permissions
|
||||
let permission = find_collect(
|
||||
&db_client().await.permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(user_id).await?,
|
||||
"$or": user_target_query(&user.id, &groups)?,
|
||||
"resource_target.type": resource_variant.as_ref(),
|
||||
"resource_target.id": resource_id
|
||||
},
|
||||
@@ -184,7 +222,7 @@ pub async fn get_user_permission_on_resource(
|
||||
.context("failed to query db for permissions")?
|
||||
.into_iter()
|
||||
// get the max permission user has between personal / any user groups
|
||||
.fold(lowest_permission, |level, permission| {
|
||||
.fold(base, |level, permission| {
|
||||
if permission.level > level {
|
||||
permission.level
|
||||
} else {
|
||||
@@ -194,15 +232,39 @@ pub async fn get_user_permission_on_resource(
|
||||
Ok(permission)
|
||||
}
|
||||
|
||||
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_resource_ids_for_non_admin(
|
||||
user_id: &str,
|
||||
pub async fn get_resource_ids_for_user(
|
||||
user: &User,
|
||||
resource_type: ResourceTargetVariant,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let permissions = find_collect(
|
||||
) -> anyhow::Result<Option<Vec<ObjectId>>> {
|
||||
// Check admin or transparent mode
|
||||
if user.admin || core_config().transparent_mode {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Check user 'all' on variant
|
||||
if let Some(level) = user.all.get(&resource_type).cloned() {
|
||||
if level > PermissionLevel::None {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Check user groups 'all' on variant
|
||||
let groups = get_user_user_groups(&user.id).await?;
|
||||
for group in &groups {
|
||||
if let Some(level) = group.all.get(&resource_type).cloned() {
|
||||
if level > PermissionLevel::None {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get specific ids
|
||||
let ids = find_collect(
|
||||
&db_client().await.permissions,
|
||||
doc! {
|
||||
"$or": user_target_query(user_id).await?,
|
||||
"$or": user_target_query(&user.id, &groups)?,
|
||||
"resource_target.type": resource_type.as_ref(),
|
||||
"level": { "$in": ["Read", "Execute", "Write"] }
|
||||
},
|
||||
@@ -213,8 +275,12 @@ pub async fn get_resource_ids_for_non_admin(
|
||||
.into_iter()
|
||||
.map(|p| p.resource_target.extract_variant_id().1.to_string())
|
||||
// collect into hashset first to remove any duplicates
|
||||
.collect::<HashSet<_>>();
|
||||
Ok(permissions.into_iter().collect())
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Some(ids))
|
||||
}
|
||||
|
||||
pub fn id_or_name_filter(id_or_name: &str) -> Document {
|
||||
@@ -224,6 +290,13 @@ pub fn id_or_name_filter(id_or_name: &str) -> Document {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id_or_username_filter(id_or_username: &str) -> Document {
|
||||
match ObjectId::from_str(id_or_username) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "username": id_or_username },
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_global_variables(
|
||||
) -> anyhow::Result<HashMap<String, String>> {
|
||||
Ok(
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::{execute::Deploy, read::GetBuildVersions},
|
||||
api::{execute::Deploy, read::ListBuildVersions},
|
||||
entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentImage, DeploymentState,
|
||||
@@ -423,7 +423,7 @@ fn extract_to_deploy_and_state<'a>(
|
||||
// Needs to only check config fields that affect docker run
|
||||
let changed = diff.server_id.is_some()
|
||||
|| diff.image.is_some()
|
||||
|| diff.image_registry.is_some()
|
||||
|| diff.image_registry_account.is_some()
|
||||
|| diff.skip_secret_interp.is_some()
|
||||
|| diff.network.is_some()
|
||||
|| diff.restart.is_some()
|
||||
@@ -467,7 +467,7 @@ fn extract_to_deploy_and_state<'a>(
|
||||
None => {
|
||||
let Some(version) = State
|
||||
.resolve(
|
||||
GetBuildVersions {
|
||||
ListBuildVersions {
|
||||
build: build_id.to_string(),
|
||||
limit: Some(1),
|
||||
..Default::default()
|
||||
|
||||
@@ -19,20 +19,27 @@ pub async fn get_remote_resources(
|
||||
String,
|
||||
)> {
|
||||
let name = to_monitor_name(&sync.name);
|
||||
let clone_args: CloneArgs = sync.into();
|
||||
let mut 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}")
|
||||
let access_token = match (&clone_args.account, &clone_args.provider) {
|
||||
(None, _) => None,
|
||||
(Some(_), None) => return Err(anyhow!("Account is configured, but provider is empty")),
|
||||
(Some(username), Some(provider)) => config
|
||||
.git_providers
|
||||
.iter()
|
||||
.find(|_provider| {
|
||||
&_provider.domain == provider
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.cloned();
|
||||
.and_then(|provider| {
|
||||
clone_args.https = provider.https;
|
||||
provider.accounts.iter().find(|account| &account.username == username).map(|account| &account.token)
|
||||
})
|
||||
.with_context(|| format!("did not find git token for account {username} | provider: {provider}"))?
|
||||
.to_owned()
|
||||
.into(),
|
||||
};
|
||||
|
||||
fs::create_dir_all(&config.sync_directory)
|
||||
.context("failed to create sync directory")?;
|
||||
@@ -44,7 +51,7 @@ pub async fn get_remote_resources(
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let mut logs =
|
||||
git::clone(clone_args, &config.sync_directory, github_token)
|
||||
git::clone(clone_args, &config.sync_directory, access_token)
|
||||
.await
|
||||
.context("failed to clone resource repo")?;
|
||||
|
||||
|
||||
@@ -7,18 +7,19 @@ use monitor_client::{
|
||||
read::ListUserTargetPermissions,
|
||||
write::{
|
||||
CreateUserGroup, DeleteUserGroup, SetUsersInUserGroup,
|
||||
UpdatePermissionOnTarget,
|
||||
UpdatePermissionOnResourceType, UpdatePermissionOnTarget,
|
||||
},
|
||||
},
|
||||
entities::{
|
||||
permission::UserTarget,
|
||||
permission::{PermissionLevel, UserTarget},
|
||||
sync::SyncUpdate,
|
||||
toml::{PermissionToml, UserGroupToml},
|
||||
update::{Log, ResourceTarget},
|
||||
update::{Log, ResourceTarget, ResourceTargetVariant},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use regex::Regex;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
@@ -28,7 +29,7 @@ use super::resource::AllResourcesById;
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
update_users: bool,
|
||||
update_permissions: bool,
|
||||
all_diff: HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
}
|
||||
|
||||
pub struct DeleteItem {
|
||||
@@ -72,19 +73,49 @@ pub async fn get_updates_for_view(
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
user_group
|
||||
.permissions
|
||||
.retain(|p| p.level > PermissionLevel::None);
|
||||
|
||||
user_group.permissions = expand_user_group_permissions(
|
||||
user_group.permissions,
|
||||
all_resources,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to expand user group {} permissions",
|
||||
user_group.name
|
||||
)
|
||||
})?;
|
||||
|
||||
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", Color::Green),
|
||||
colored(&user_group.name, Color::Green),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
user_group.permissions,
|
||||
));
|
||||
if user_group.all.is_empty() {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:#?}\n{}: {:#?}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&user_group.name, Color::Green),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
user_group.permissions,
|
||||
));
|
||||
} else {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:#?}\n{}: {:#?}\n{}: {:#?}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&user_group.name, Color::Green),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("base permissions"),
|
||||
user_group.all,
|
||||
muted("permissions"),
|
||||
user_group.permissions,
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -107,6 +138,7 @@ pub async fn get_updates_for_view(
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.filter(|p| p.level > PermissionLevel::None)
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
@@ -185,22 +217,27 @@ pub async fn get_updates_for_view(
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
let all_diff = diff_group_all(&original.all, &user_group.all);
|
||||
|
||||
user_group.permissions.sort_by(sort_permissions);
|
||||
original_permissions.sort_by(sort_permissions);
|
||||
|
||||
let update_users = user_group.users != original_users;
|
||||
let update_all = !all_diff.is_empty();
|
||||
let update_permissions =
|
||||
user_group.permissions != original_permissions;
|
||||
|
||||
// only add log after diff detected
|
||||
if update_users || update_permissions {
|
||||
if update_users || update_all || update_permissions {
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("UPDATE", Color::Blue),
|
||||
bold(&user_group.name),
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
|
||||
if update_users {
|
||||
let adding = user_group
|
||||
.users
|
||||
@@ -230,12 +267,36 @@ pub async fn get_updates_for_view(
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
|
||||
if update_all {
|
||||
let updates = all_diff
|
||||
.into_iter()
|
||||
.map(|(variant, (orig, incoming))| {
|
||||
format!(
|
||||
"{}: {} {} {}",
|
||||
bold(variant),
|
||||
colored(orig, Color::Red),
|
||||
muted("->"),
|
||||
colored(incoming, Color::Green)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
lines.push(format!(
|
||||
"{}: 'base permission'\n{updates}",
|
||||
muted("field"),
|
||||
))
|
||||
}
|
||||
|
||||
if update_permissions {
|
||||
let adding = user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!original_permissions.contains(permission)
|
||||
// add if original has no exising permission on the target
|
||||
!original_permissions
|
||||
.iter()
|
||||
.any(|p| p.target == permission.target)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -244,10 +305,35 @@ pub async fn get_updates_for_view(
|
||||
} else {
|
||||
colored(&adding.join(", "), Color::Green)
|
||||
};
|
||||
let updating = user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
// update if original has exising permission on the target with different level
|
||||
let Some(level) = original_permissions
|
||||
.iter()
|
||||
.find(|p| p.target == permission.target)
|
||||
.map(|p| p.level)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
permission.level != level
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
let updating = if updating.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&updating.join(", "), Color::Blue)
|
||||
};
|
||||
let removing = original_permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!user_group.permissions.contains(permission)
|
||||
// remove if incoming has no permission on the target
|
||||
!user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.any(|p| p.target == permission.target)
|
||||
})
|
||||
.map(|permission| format!("{permission:?}"))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -257,12 +343,14 @@ pub async fn get_updates_for_view(
|
||||
colored(&removing.join(", "), Color::Red)
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {adding}",
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {updating}\n{}: {adding}",
|
||||
muted("field"),
|
||||
muted("removing"),
|
||||
muted("updating"),
|
||||
muted("adding"),
|
||||
))
|
||||
}
|
||||
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
@@ -326,6 +414,22 @@ pub async fn get_updates_for_execution(
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for mut user_group in user_groups {
|
||||
user_group
|
||||
.permissions
|
||||
.retain(|p| p.level > PermissionLevel::None);
|
||||
|
||||
user_group.permissions = expand_user_group_permissions(
|
||||
user_group.permissions,
|
||||
all_resources,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to expand user group {} permissions",
|
||||
user_group.name
|
||||
)
|
||||
})?;
|
||||
|
||||
let original = match map.get(&user_group.name).cloned() {
|
||||
Some(original) => original,
|
||||
None => {
|
||||
@@ -352,6 +456,7 @@ pub async fn get_updates_for_execution(
|
||||
.await
|
||||
.context("failed to query for existing UserGroup permissions")?
|
||||
.into_iter()
|
||||
.filter(|p| p.level > PermissionLevel::None)
|
||||
.map(|mut p| {
|
||||
// replace the ids with names
|
||||
match &mut p.resource_target {
|
||||
@@ -430,19 +535,55 @@ pub async fn get_updates_for_execution(
|
||||
original_users.sort();
|
||||
user_group.users.sort();
|
||||
|
||||
let all_diff = diff_group_all(&original.all, &user_group.all);
|
||||
|
||||
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 {
|
||||
// Extend permissions with any existing that have no target in incoming
|
||||
let to_remove = original_permissions
|
||||
.iter()
|
||||
.filter(|permission| {
|
||||
!user_group
|
||||
.permissions
|
||||
.iter()
|
||||
.any(|p| p.target == permission.target)
|
||||
})
|
||||
.map(|permission| PermissionToml {
|
||||
target: permission.target.clone(),
|
||||
level: PermissionLevel::None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
user_group.permissions.extend(to_remove);
|
||||
|
||||
// remove any permissions that already exist on original
|
||||
user_group.permissions.retain(|permission| {
|
||||
let Some(level) = original_permissions
|
||||
.iter()
|
||||
.find(|p| p.target == permission.target)
|
||||
.map(|p| p.level)
|
||||
else {
|
||||
// not in original, keep it
|
||||
return true;
|
||||
};
|
||||
// keep it if level doesn't match
|
||||
level != permission.level
|
||||
});
|
||||
|
||||
// only push update after diff detected
|
||||
if update_users
|
||||
|| !all_diff.is_empty()
|
||||
|| !user_group.permissions.is_empty()
|
||||
{
|
||||
to_update.push(UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
all_diff: all_diff
|
||||
.into_iter()
|
||||
.map(|(k, (_, v))| (k, v))
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -516,6 +657,13 @@ pub async fn run_updates(
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_all(
|
||||
user_group.name.clone(),
|
||||
user_group.all,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
@@ -529,7 +677,7 @@ pub async fn run_updates(
|
||||
for UpdateItem {
|
||||
user_group,
|
||||
update_users,
|
||||
update_permissions,
|
||||
all_diff,
|
||||
} in to_update
|
||||
{
|
||||
if update_users {
|
||||
@@ -541,7 +689,16 @@ pub async fn run_updates(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if update_permissions {
|
||||
if !all_diff.is_empty() {
|
||||
run_update_all(
|
||||
user_group.name.clone(),
|
||||
all_diff,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if !user_group.permissions.is_empty() {
|
||||
run_update_permissions(
|
||||
user_group.name,
|
||||
user_group.permissions,
|
||||
@@ -616,6 +773,41 @@ async fn set_users(
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_update_all(
|
||||
user_group: String,
|
||||
all_diff: HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
log: &mut String,
|
||||
has_error: &mut bool,
|
||||
) {
|
||||
for (resource_type, permission) in all_diff {
|
||||
if let Err(e) = State
|
||||
.resolve(
|
||||
UpdatePermissionOnResourceType {
|
||||
user_target: UserTarget::UserGroup(user_group.clone()),
|
||||
resource_type,
|
||||
permission,
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set base permissions on {resource_type} in group {} | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' base permissions on {resource_type}",
|
||||
muted("INFO"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_update_permissions(
|
||||
user_group: String,
|
||||
permissions: Vec<PermissionToml>,
|
||||
@@ -636,17 +828,188 @@ async fn run_update_permissions(
|
||||
{
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set permssion in group {} | target: {target:?} | {e:#}",
|
||||
"\n{}: failed to set permission in group {} | target: {target:?} | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' permissions",
|
||||
"\n{}: {} user group '{}' permissions | {}: {target:?} | {}: {level}",
|
||||
muted("INFO"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
bold(&user_group),
|
||||
muted("target"),
|
||||
muted("level")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands any regex defined targets into the full list
|
||||
async fn expand_user_group_permissions(
|
||||
permissions: Vec<PermissionToml>,
|
||||
all_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Vec<PermissionToml>> {
|
||||
let mut expanded =
|
||||
Vec::<PermissionToml>::with_capacity(permissions.capacity());
|
||||
|
||||
for permission in permissions {
|
||||
let (variant, id) = permission.target.extract_variant_id();
|
||||
if id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if id.starts_with('\\') && id.ends_with('\\') {
|
||||
let inner = &id[1..(id.len() - 1)];
|
||||
let regex = Regex::new(inner)
|
||||
.with_context(|| format!("invalid regex. got: {inner}"))?;
|
||||
match variant {
|
||||
ResourceTargetVariant::Build => {
|
||||
let permissions = all_resources
|
||||
.builds
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Build(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Builder => {
|
||||
let permissions = all_resources
|
||||
.builders
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Builder(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Deployment => {
|
||||
let permissions = all_resources
|
||||
.deployments
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Deployment(
|
||||
resource.name.clone(),
|
||||
),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Server => {
|
||||
let permissions = all_resources
|
||||
.servers
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Server(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Repo => {
|
||||
let permissions = all_resources
|
||||
.repos
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Repo(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Alerter => {
|
||||
let permissions = all_resources
|
||||
.alerters
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Alerter(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Procedure => {
|
||||
let permissions = all_resources
|
||||
.procedures
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Procedure(
|
||||
resource.name.clone(),
|
||||
),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::ServerTemplate => {
|
||||
let permissions = all_resources
|
||||
.templates
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::ServerTemplate(
|
||||
resource.name.clone(),
|
||||
),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::ResourceSync => {
|
||||
let permissions = all_resources
|
||||
.syncs
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::ResourceSync(
|
||||
resource.name.clone(),
|
||||
),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::System => {}
|
||||
}
|
||||
} else {
|
||||
// No regex
|
||||
expanded.push(permission);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expanded)
|
||||
}
|
||||
|
||||
type AllDiff =
|
||||
HashMap<ResourceTargetVariant, (PermissionLevel, PermissionLevel)>;
|
||||
|
||||
/// diffs user_group.all
|
||||
fn diff_group_all(
|
||||
original: &HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
incoming: &HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
) -> AllDiff {
|
||||
let mut to_update = HashMap::new();
|
||||
|
||||
// need to compare both forward and backward because either hashmap could be sparse.
|
||||
|
||||
// forward direction
|
||||
for (variant, level) in incoming {
|
||||
let original_level = original.get(variant).unwrap_or_default();
|
||||
if level == original_level {
|
||||
continue;
|
||||
}
|
||||
to_update.insert(*variant, (*original_level, *level));
|
||||
}
|
||||
|
||||
// backward direction
|
||||
for (variant, level) in original {
|
||||
let incoming_level = incoming.get(variant).unwrap_or_default();
|
||||
if level == incoming_level {
|
||||
continue;
|
||||
}
|
||||
to_update.insert(*variant, (*level, *incoming_level));
|
||||
}
|
||||
|
||||
to_update
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ async fn verify_gh_signature(
|
||||
}
|
||||
let signature = signature.unwrap().replace("sha256=", "");
|
||||
let mut mac = HmacSha256::new_from_slice(
|
||||
core_config().github_webhook_secret.as_bytes(),
|
||||
core_config().webhook_secret.as_bytes(),
|
||||
)
|
||||
.expect("github webhook | failed to create hmac sha256");
|
||||
mac.update(body.as_bytes());
|
||||
|
||||
@@ -146,8 +146,8 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
) => {
|
||||
// modify alert level
|
||||
if alert.level != health.cpu {
|
||||
// modify alert level only if it has increased
|
||||
if alert.level < health.cpu {
|
||||
alert.level = health.cpu;
|
||||
alert.data = AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
@@ -218,7 +218,8 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
) => {
|
||||
if alert.level != health.mem {
|
||||
// modify alert level only if it has increased
|
||||
if alert.level < health.mem {
|
||||
alert.level = health.mem;
|
||||
alert.data = AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
@@ -299,6 +300,7 @@ pub async fn alert_servers(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
) => {
|
||||
// Disk is persistent, update alert if health changes regardless of direction
|
||||
if *health != alert.level {
|
||||
let disk =
|
||||
server_status.stats.as_ref().and_then(|stats| {
|
||||
|
||||
@@ -53,6 +53,7 @@ impl super::MonitorResource for Build {
|
||||
info: BuildListItemInfo {
|
||||
last_built_at: build.info.last_built_at,
|
||||
version: build.config.version,
|
||||
git_provider: build.config.git_provider,
|
||||
repo: build.config.repo,
|
||||
branch: build.config.branch,
|
||||
state,
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::{
|
||||
helpers::{
|
||||
create_permission, flatten_document,
|
||||
query::{
|
||||
get_resource_ids_for_non_admin, get_tag,
|
||||
get_resource_ids_for_user, get_tag,
|
||||
get_user_permission_on_resource, id_or_name_filter,
|
||||
},
|
||||
update::{add_update, make_update},
|
||||
@@ -211,7 +211,7 @@ pub async fn get_check_permissions<T: MonitorResource>(
|
||||
return Ok(resource);
|
||||
}
|
||||
let permissions = get_user_permission_on_resource(
|
||||
&user.id,
|
||||
user,
|
||||
T::resource_type(),
|
||||
&resource.id,
|
||||
)
|
||||
@@ -265,13 +265,9 @@ async fn list_full_for_user_using_document<T: MonitorResource>(
|
||||
mut filters: Document,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
|
||||
if !user.admin && !core_config().transparent_mode {
|
||||
let ids =
|
||||
get_resource_ids_for_non_admin(&user.id, T::resource_type())
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|id| ObjectId::from_str(&id))
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(ids) =
|
||||
get_resource_ids_for_user(user, T::resource_type()).await?
|
||||
{
|
||||
filters.insert("_id", doc! { "$in": ids });
|
||||
}
|
||||
find_collect(
|
||||
@@ -612,12 +608,10 @@ where
|
||||
if let Err(e) = db_client()
|
||||
.await
|
||||
.permissions
|
||||
.delete_many(
|
||||
doc! {
|
||||
"resource_target.type": variant.as_ref(),
|
||||
"resource_target.id": &id
|
||||
},
|
||||
)
|
||||
.delete_many(doc! {
|
||||
"resource_target.type": variant.as_ref(),
|
||||
"resource_target.id": &id
|
||||
})
|
||||
.await
|
||||
{
|
||||
warn!("failed to delete_many permissions matching target {target:?} | {e:#}");
|
||||
|
||||
@@ -60,6 +60,7 @@ impl super::MonitorResource for Repo {
|
||||
info: RepoListItemInfo {
|
||||
server_id: repo.config.server_id,
|
||||
last_pulled_at: repo.info.last_pulled_at,
|
||||
git_provider: repo.config.git_provider,
|
||||
repo: repo.config.repo,
|
||||
branch: repo.config.branch,
|
||||
state,
|
||||
|
||||
@@ -54,6 +54,7 @@ impl super::MonitorResource for ResourceSync {
|
||||
tags: resource_sync.tags,
|
||||
resource_type: ResourceTargetVariant::ResourceSync,
|
||||
info: ResourceSyncListItemInfo {
|
||||
git_provider: resource_sync.config.git_provider,
|
||||
repo: resource_sync.config.repo,
|
||||
branch: resource_sync.config.branch,
|
||||
last_sync_ts: resource_sync.info.last_sync_ts,
|
||||
|
||||
+70
-3
@@ -1,10 +1,20 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
build::BuildState, deployment::DeploymentState,
|
||||
procedure::ProcedureState, repo::RepoState,
|
||||
build::BuildState,
|
||||
config::core::{CoreConfig, GithubWebhookAppConfig},
|
||||
deployment::DeploymentState,
|
||||
procedure::ProcedureState,
|
||||
repo::RepoState,
|
||||
sync::ResourceSyncState,
|
||||
};
|
||||
use octorust::auth::{
|
||||
Credentials, InstallationTokenGenerator, JWTCredentials,
|
||||
};
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
|
||||
use crate::{
|
||||
@@ -36,6 +46,63 @@ pub fn jwt_client() -> &'static JwtClient {
|
||||
JWT_CLIENT.get_or_init(|| JwtClient::new(core_config()))
|
||||
}
|
||||
|
||||
pub fn github_client(
|
||||
) -> Option<&'static HashMap<String, octorust::Client>> {
|
||||
static GITHUB_CLIENT: OnceLock<
|
||||
Option<HashMap<String, octorust::Client>>,
|
||||
> = OnceLock::new();
|
||||
GITHUB_CLIENT
|
||||
.get_or_init(|| {
|
||||
let CoreConfig {
|
||||
github_webhook_app:
|
||||
GithubWebhookAppConfig {
|
||||
app_id,
|
||||
installations,
|
||||
pk_path,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = core_config();
|
||||
if *app_id == 0 || installations.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let private_key = std::fs::read(pk_path)
|
||||
.context("github webhook app | failed to load private key")
|
||||
.unwrap();
|
||||
|
||||
let private_key = nom_pem::decode_block(&private_key)
|
||||
.map_err(|e| anyhow!("{e:?}"))
|
||||
.context("github webhook app | failed to decode private key")
|
||||
.unwrap();
|
||||
|
||||
let jwt = JWTCredentials::new(*app_id, private_key.data)
|
||||
.context(
|
||||
"github webhook app | failed to make github JWTCredentials",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut clients =
|
||||
HashMap::with_capacity(installations.capacity());
|
||||
|
||||
for installation in installations {
|
||||
let token_generator = InstallationTokenGenerator::new(
|
||||
installation.id,
|
||||
jwt.clone(),
|
||||
);
|
||||
let client = octorust::Client::new(
|
||||
"github-app",
|
||||
Credentials::InstallationToken(token_generator),
|
||||
)
|
||||
.context("failed to initialize github client")
|
||||
.unwrap();
|
||||
clients.insert(installation.namespace.to_string(), client);
|
||||
}
|
||||
|
||||
Some(clients)
|
||||
})
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
pub fn action_states() -> &'static ActionStates {
|
||||
static ACTION_STATES: OnceLock<ActionStates> = OnceLock::new();
|
||||
ACTION_STATES.get_or_init(ActionStates::default)
|
||||
|
||||
+1
-1
@@ -208,7 +208,7 @@ async fn user_can_see_update(
|
||||
}
|
||||
let (variant, id) = update_target.extract_variant_id();
|
||||
let permissions =
|
||||
get_user_permission_on_resource(&user.id, variant, id).await?;
|
||||
get_user_permission_on_resource(user, variant, id).await?;
|
||||
if permissions > PermissionLevel::None {
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM rust:1.79.0-bookworm AS builder
|
||||
WORKDIR /builder
|
||||
COPY . .
|
||||
RUN cargo build -p migrator --release
|
||||
|
||||
# Final Image
|
||||
FROM gcr.io/distroless/cc-debian12
|
||||
|
||||
COPY --from=builder /builder/target/release/migrator /
|
||||
|
||||
# Label for Ghcr
|
||||
LABEL org.opencontainers.image.source=https://github.com/mbecker20/monitor
|
||||
LABEL org.opencontainers.image.description="Database migrator for Monitor version upgrades"
|
||||
LABEL org.opencontainers.image.licenses=GPL-3.0
|
||||
|
||||
CMD ["./migrator"]
|
||||
+36
-2
@@ -1,5 +1,39 @@
|
||||
# Migrator
|
||||
|
||||
Upgrade data between periphery versions.
|
||||
Performs schema changes on the Monitor database
|
||||
|
||||
Supports v0.x -> v1.x migration.
|
||||
## v1.7 - v1.11 migration
|
||||
Run this before upgrading to latest from versions 1.7 to 1.11.
|
||||
```sh
|
||||
docker run --rm --name monitor-migrator \
|
||||
--env MIGRATION="v1.11" \
|
||||
--env TARGET_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env TARGET_DB_NAME="<DB_NAME>" \
|
||||
ghcr.io/mbecker20/monitor_migrator
|
||||
```
|
||||
|
||||
## v1.0 - v1.6 migration
|
||||
Run this before upgrading to latest from versions 1.0 to 1.6.
|
||||
```sh
|
||||
docker run --rm --name monitor-migrator \
|
||||
--env MIGRATION="v1.6" \
|
||||
--env TARGET_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env TARGET_DB_NAME="<DB_NAME>" \
|
||||
ghcr.io/mbecker20/monitor_migrator
|
||||
```
|
||||
|
||||
## v0.X migration
|
||||
Run this before upgrading to latest from version 0.X.
|
||||
|
||||
Note. As this is a major upgrade, this migration is not "in place".
|
||||
It will create another database (TARGET) and migrate resources over, leaving the original database (LEGACY) unchanged.
|
||||
|
||||
```sh
|
||||
docker run --rm --name monitor-migrator \
|
||||
--env MIGRATION="v0" \
|
||||
--env TARGET_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env TARGET_DB_NAME="<TARGET_DB_NAME>" \
|
||||
--env LEGACY_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env LEGACY_DB_NAME="<LEGACY_DB_NAME>" \
|
||||
ghcr.io/mbecker20/monitor_migrator
|
||||
```
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(unused)]
|
||||
pub mod v0;
|
||||
pub mod v1_11;
|
||||
pub mod v1_6;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::build::{
|
||||
BuildConfig, BuildInfo, CloudRegistryConfig, ImageRegistry,
|
||||
BuildConfig, BuildInfo, ImageRegistry, StandardRegistryConfig,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -207,11 +207,14 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
|
||||
builder_id: String::new(),
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
version: value.version.into(),
|
||||
git_provider: String::from("github.com"),
|
||||
git_https: true,
|
||||
repo: value.repo.unwrap_or_default(),
|
||||
branch: value.branch.unwrap_or_default(),
|
||||
github_account: value.github_account.unwrap_or_default(),
|
||||
image_registry: ImageRegistry::DockerHub(
|
||||
CloudRegistryConfig {
|
||||
git_account: value.github_account.unwrap_or_default(),
|
||||
image_registry: ImageRegistry::Standard(
|
||||
StandardRegistryConfig {
|
||||
domain: String::from("docker.io"),
|
||||
account: value.docker_account.unwrap_or_default(),
|
||||
organization: value
|
||||
.docker_organization
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
build::{ImageRegistry, StandardRegistryConfig},
|
||||
NoData,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
@@ -378,15 +378,10 @@ impl TryFrom<Deployment>
|
||||
.post_image
|
||||
.unwrap_or_default(),
|
||||
extra_args: value.docker_run_args.extra_args,
|
||||
image_registry: match value.docker_run_args.docker_account {
|
||||
Some(account) => {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
None => ImageRegistry::None(NoData {}),
|
||||
},
|
||||
image_registry_account: value
|
||||
.docker_run_args
|
||||
.docker_account
|
||||
.unwrap_or_default(),
|
||||
labels: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -106,11 +106,8 @@ impl TryFrom<User> for monitor_client::entities::user::User {
|
||||
create_server_permissions: value.create_server_permissions,
|
||||
create_build_permissions: value.create_build_permissions,
|
||||
last_update_view: Default::default(),
|
||||
recent_servers: Vec::new(),
|
||||
recent_deployments: Vec::new(),
|
||||
recent_builds: Vec::new(),
|
||||
recent_repos: Vec::new(),
|
||||
recent_procedures: Vec::new(),
|
||||
recents: Default::default(),
|
||||
all: Default::default(),
|
||||
updated_at: unix_from_monitor_ts(&value.updated_at)?,
|
||||
};
|
||||
Ok(user)
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
use monitor_client::entities::{
|
||||
build::StandardRegistryConfig, EnvironmentVar, NoData,
|
||||
SystemCommand, Version, I64,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::resource::Resource;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
/// Which builder is used to build the image.
|
||||
#[serde(default, alias = "builder")]
|
||||
pub builder_id: String,
|
||||
|
||||
/// 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 optional command run after repo clone and before docker build.
|
||||
#[serde(default)]
|
||||
pub pre_build: SystemCommand,
|
||||
|
||||
/// Configuration for the registry to push the built image to.
|
||||
#[serde(default)]
|
||||
pub image_registry: ImageRegistry,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Whether to skip secret interpolation in the build_args.
|
||||
#[serde(default)]
|
||||
pub skip_secret_interp: bool,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Any extra docker cli arguments to be included in the build command
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// Docker build arguments.
|
||||
///
|
||||
/// These values are visible in the final image by running `docker inspect`.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "monitor_client::entities::env_vars_deserializer"
|
||||
)]
|
||||
pub build_args: Vec<EnvironmentVar>,
|
||||
|
||||
/// Secret arguments.
|
||||
///
|
||||
/// These values remain hidden in the final image by using
|
||||
/// docker secret mounts. See `<https://docs.docker.com/build/building/secrets>`.
|
||||
///
|
||||
/// The values can be used in RUN commands:
|
||||
/// ```
|
||||
/// RUN --mount=type=secret,id=SECRET_KEY \
|
||||
/// SECRET_KEY=$(cat /run/secrets/SECRET_KEY) ...
|
||||
/// ```
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "monitor_client::entities::env_vars_deserializer"
|
||||
)]
|
||||
pub secret_args: Vec<EnvironmentVar>,
|
||||
|
||||
/// Docker labels
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "monitor_client::entities::env_vars_deserializer"
|
||||
)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
git_provider: String::from("github.com"),
|
||||
git_https: true,
|
||||
repo: value.repo,
|
||||
branch: value.branch,
|
||||
commit: value.commit,
|
||||
git_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(),
|
||||
secret_args: Default::default(),
|
||||
labels: value.labels.into_iter().map(Into::into).collect(),
|
||||
extra_args: value.extra_args,
|
||||
use_buildx: value.use_buildx,
|
||||
webhook_enabled: value.webhook_enabled,
|
||||
image_registry: value.image_registry.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
pub enum ImageRegistry {
|
||||
/// Don't push the image to any registry
|
||||
None(NoData),
|
||||
/// Push the image to DockerHub
|
||||
DockerHub(CloudRegistryConfig),
|
||||
/// Push the image to the Github Container Registry.
|
||||
///
|
||||
/// See [the Github docs](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images)
|
||||
/// for information on creating an access token
|
||||
Ghcr(CloudRegistryConfig),
|
||||
/// Push the image to Aws Elastic Container Registry
|
||||
///
|
||||
/// The string held in 'params' should match a label of an `aws_ecr_registry` in the core config.
|
||||
AwsEcr(String),
|
||||
/// Todo. Will point to a custom "Registry" resource by id
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Default for ImageRegistry {
|
||||
fn default() -> Self {
|
||||
Self::None(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageRegistry>
|
||||
for monitor_client::entities::build::ImageRegistry
|
||||
{
|
||||
fn from(value: ImageRegistry) -> Self {
|
||||
match value {
|
||||
ImageRegistry::None(_) | ImageRegistry::Custom(_) => {
|
||||
monitor_client::entities::build::ImageRegistry::None(
|
||||
NoData {},
|
||||
)
|
||||
}
|
||||
ImageRegistry::DockerHub(params) => {
|
||||
monitor_client::entities::build::ImageRegistry::Standard(
|
||||
StandardRegistryConfig {
|
||||
domain: String::from("docker.io"),
|
||||
account: params.account,
|
||||
organization: params.organization,
|
||||
},
|
||||
)
|
||||
}
|
||||
ImageRegistry::Ghcr(params) => {
|
||||
monitor_client::entities::build::ImageRegistry::Standard(
|
||||
StandardRegistryConfig {
|
||||
domain: String::from("ghcr.io"),
|
||||
account: params.account,
|
||||
organization: params.organization,
|
||||
},
|
||||
)
|
||||
}
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
monitor_client::entities::build::ImageRegistry::AwsEcr(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct CloudRegistryConfig {
|
||||
/// Specify an account to use with the cloud registry.
|
||||
#[serde(default)]
|
||||
pub account: String,
|
||||
|
||||
/// Optional. Specify an organization to push the image under.
|
||||
/// Empty string means no organization.
|
||||
#[serde(default)]
|
||||
pub organization: String,
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
use monitor_client::entities::{
|
||||
deployment::{
|
||||
conversions_deserializer, term_labels_deserializer, Conversion,
|
||||
DeploymentImage, RestartMode, TerminationSignal,
|
||||
TerminationSignalLabel,
|
||||
},
|
||||
env_vars_deserializer, EnvironmentVar,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{build::ImageRegistry, resource::Resource};
|
||||
|
||||
pub type Deployment = Resource<DeploymentConfig, ()>;
|
||||
|
||||
impl From<Deployment>
|
||||
for monitor_client::entities::deployment::Deployment
|
||||
{
|
||||
fn from(value: Deployment) -> Self {
|
||||
monitor_client::entities::deployment::Deployment {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
updated_at: value.updated_at,
|
||||
tags: value.tags,
|
||||
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,
|
||||
|
||||
/// The image which the deployment deploys.
|
||||
/// Can either be a user inputted image, or a Monitor build.
|
||||
#[serde(default)]
|
||||
pub image: DeploymentImage,
|
||||
|
||||
/// Configure the registry used to pull the image from the registry.
|
||||
/// Used with `docker login`.
|
||||
///
|
||||
/// When using attached build as image source:
|
||||
/// - If the field is `None` variant, will use the same ImageRegistry config as the build.
|
||||
/// - Otherwise, it must match the variant of the ImageRegistry build config.
|
||||
/// - Only the account is used, the organization is not needed here
|
||||
#[serde(default)]
|
||||
pub image_registry: ImageRegistry,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Whether to send ContainerStateChange alerts for this deployment.
|
||||
#[serde(default = "default_send_alerts")]
|
||||
pub send_alerts: bool,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Extra args which are interpolated into the `docker run` command,
|
||||
/// and affect the container configuration.
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// Labels attached to various termination signal options.
|
||||
/// Used to specify different shutdown functionality depending on the termination signal.
|
||||
#[serde(
|
||||
default = "default_term_signal_labels",
|
||||
deserialize_with = "term_labels_deserializer"
|
||||
)]
|
||||
pub term_signal_labels: Vec<TerminationSignalLabel>,
|
||||
|
||||
/// The container port mapping.
|
||||
/// Irrelevant if container network is `host`.
|
||||
/// Maps ports on host to ports on container.
|
||||
#[serde(default, deserialize_with = "conversions_deserializer")]
|
||||
pub ports: Vec<Conversion>,
|
||||
|
||||
/// The container volume mapping.
|
||||
/// Maps files / folders on host to files / folders in container.
|
||||
#[serde(default, deserialize_with = "conversions_deserializer")]
|
||||
pub volumes: Vec<Conversion>,
|
||||
|
||||
/// The environment variables passed to the container.
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
pub environment: Vec<EnvironmentVar>,
|
||||
|
||||
/// The docker labels given to the container.
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
}
|
||||
|
||||
fn default_send_alerts() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_term_signal_labels() -> Vec<TerminationSignalLabel> {
|
||||
vec![TerminationSignalLabel::default()]
|
||||
}
|
||||
|
||||
fn default_termination_timeout() -> i32 {
|
||||
10
|
||||
}
|
||||
|
||||
fn default_network() -> String {
|
||||
String::from("host")
|
||||
}
|
||||
|
||||
impl From<DeploymentConfig>
|
||||
for monitor_client::entities::deployment::DeploymentConfig
|
||||
{
|
||||
fn from(value: DeploymentConfig) -> Self {
|
||||
monitor_client::entities::deployment::DeploymentConfig {
|
||||
server_id: value.server_id,
|
||||
image: value.image,
|
||||
image_registry_account: match value.image_registry {
|
||||
ImageRegistry::None(_)
|
||||
| ImageRegistry::AwsEcr(_)
|
||||
| ImageRegistry::Custom(_) => String::new(),
|
||||
ImageRegistry::DockerHub(params) => params.account,
|
||||
ImageRegistry::Ghcr(params) => params.account,
|
||||
},
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
redeploy_on_build: value.redeploy_on_build,
|
||||
send_alerts: value.send_alerts,
|
||||
network: value.network,
|
||||
restart: value.restart,
|
||||
command: value.command,
|
||||
termination_signal: value.termination_signal,
|
||||
termination_timeout: value.termination_timeout,
|
||||
extra_args: value.extra_args,
|
||||
term_signal_labels: value.term_signal_labels,
|
||||
ports: value.ports,
|
||||
volumes: value.volumes,
|
||||
environment: value.environment,
|
||||
labels: value.labels,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use mungos::{init::MongoBuilder, mongodb::Collection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod build;
|
||||
pub mod deployment;
|
||||
pub mod resource;
|
||||
|
||||
pub struct DbClient {
|
||||
pub builds: Collection<build::Build>,
|
||||
pub deployments: Collection<deployment::Deployment>,
|
||||
}
|
||||
|
||||
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 {
|
||||
builds: db.collection("Build"),
|
||||
deployments: db.collection("Deployment"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
build::{ImageRegistry, StandardRegistryConfig},
|
||||
NoData,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -119,10 +119,12 @@ impl From<BuildConfig>
|
||||
minor: value.version.minor,
|
||||
patch: value.version.patch,
|
||||
},
|
||||
git_provider: String::from("github.com"),
|
||||
git_https: true,
|
||||
repo: value.repo,
|
||||
branch: value.branch,
|
||||
commit: value.commit,
|
||||
github_account: value.github_account,
|
||||
git_account: value.github_account,
|
||||
pre_build: monitor_client::entities::SystemCommand {
|
||||
path: value.pre_build.path,
|
||||
command: value.pre_build.command,
|
||||
@@ -141,7 +143,8 @@ impl From<BuildConfig>
|
||||
image_registry: if value.docker_account.is_empty() {
|
||||
ImageRegistry::None(NoData {})
|
||||
} else {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
ImageRegistry::Standard(StandardRegistryConfig {
|
||||
domain: String::from("docker.io"),
|
||||
account: value.docker_account,
|
||||
organization: value.docker_organization,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
deployment::{
|
||||
Conversion, DeploymentImage, RestartMode, TerminationSignal,
|
||||
TerminationSignalLabel,
|
||||
},
|
||||
NoData,
|
||||
use monitor_client::entities::deployment::{
|
||||
Conversion, DeploymentImage, RestartMode, TerminationSignal,
|
||||
TerminationSignalLabel,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -118,14 +114,7 @@ impl From<DeploymentConfig>
|
||||
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()
|
||||
})
|
||||
},
|
||||
image_registry_account: value.docker_account,
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
redeploy_on_build: value.redeploy_on_build,
|
||||
term_signal_labels: value.term_signal_labels,
|
||||
|
||||
@@ -12,14 +12,18 @@ mod legacy;
|
||||
mod migrate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum AppMode {
|
||||
enum Migration {
|
||||
#[serde(alias = "v0")]
|
||||
V0,
|
||||
#[serde(alias = "v1.6")]
|
||||
V1_6,
|
||||
#[serde(alias = "v1.11")]
|
||||
V1_11,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
app_mode: AppMode,
|
||||
migration: Migration,
|
||||
legacy_uri: String,
|
||||
legacy_db_name: String,
|
||||
target_uri: String,
|
||||
@@ -35,8 +39,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let env: Env = envy::from_env()?;
|
||||
|
||||
match env.app_mode {
|
||||
AppMode::V0 => {
|
||||
match env.migration {
|
||||
Migration::V0 => {
|
||||
let legacy_db = legacy::v0::DbClient::new(
|
||||
&env.legacy_uri,
|
||||
&env.legacy_db_name,
|
||||
@@ -46,7 +50,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
DbClient::new(&env.target_uri, &env.target_db_name).await?;
|
||||
migrate::v0::migrate_all(&legacy_db, &target_db).await?
|
||||
}
|
||||
AppMode::V1_6 => {
|
||||
Migration::V1_6 => {
|
||||
let db = legacy::v1_6::DbClient::new(
|
||||
&env.target_uri,
|
||||
&env.target_db_name,
|
||||
@@ -54,6 +58,14 @@ async fn main() -> anyhow::Result<()> {
|
||||
.await;
|
||||
migrate::v1_6::migrate_all_in_place(&db).await?
|
||||
}
|
||||
Migration::V1_11 => {
|
||||
let db = legacy::v1_11::DbClient::new(
|
||||
&env.target_uri,
|
||||
&env.target_db_name,
|
||||
)
|
||||
.await;
|
||||
migrate::v1_11::migrate_all_in_place(&db).await?
|
||||
}
|
||||
}
|
||||
|
||||
info!("finished!");
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod v0;
|
||||
pub mod v1_11;
|
||||
pub mod v1_6;
|
||||
|
||||
@@ -7,7 +7,9 @@ use monitor_client::entities::{
|
||||
update::{ResourceTarget, Update},
|
||||
user::User,
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use mungos::{
|
||||
find::find_collect, mongodb::options::InsertManyOptions,
|
||||
};
|
||||
|
||||
use crate::legacy::v0;
|
||||
|
||||
@@ -28,13 +30,22 @@ pub async fn migrate_users(
|
||||
legacy_db: &v0::DbClient,
|
||||
target_db: &crate::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let existing = find_collect(&target_db.users, None, None)
|
||||
.await
|
||||
.context("failed to get existing target users")?;
|
||||
|
||||
let users = find_collect(&legacy_db.users, None, None)
|
||||
.await
|
||||
.context("failed to get legacy users")?
|
||||
.into_iter()
|
||||
.filter_map(|s| {
|
||||
let username = s.username.clone();
|
||||
s.try_into()
|
||||
.filter_map(|user| {
|
||||
if existing.iter().any(|u| u.username == user.username) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let username = user.username.clone();
|
||||
user
|
||||
.try_into()
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to convert user {username} | {e:#}")
|
||||
})
|
||||
@@ -59,6 +70,10 @@ pub async fn migrate_servers(
|
||||
legacy_db: &v0::DbClient,
|
||||
target_db: &crate::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let existing = find_collect(&target_db.servers, None, None)
|
||||
.await
|
||||
.context("failed to get existing target servers")?;
|
||||
|
||||
let servers = find_collect(&legacy_db.servers, None, None)
|
||||
.await
|
||||
.context("failed to get legacy servers")?;
|
||||
@@ -67,6 +82,10 @@ pub async fn migrate_servers(
|
||||
let mut permissions = Vec::<Permission>::new();
|
||||
|
||||
for server in servers {
|
||||
if existing.iter().any(|s| s.name == server.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (user_id, level) in &server.permissions {
|
||||
let permission = Permission {
|
||||
id: Default::default(),
|
||||
@@ -92,6 +111,9 @@ pub async fn migrate_servers(
|
||||
target_db
|
||||
.servers
|
||||
.insert_many(new_servers)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert servers on target")?;
|
||||
}
|
||||
@@ -100,6 +122,9 @@ pub async fn migrate_servers(
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert server permissions on target")?;
|
||||
}
|
||||
@@ -113,6 +138,10 @@ pub async fn migrate_deployments(
|
||||
legacy_db: &v0::DbClient,
|
||||
target_db: &crate::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let existing = find_collect(&target_db.deployments, None, None)
|
||||
.await
|
||||
.context("failed to get existing target deployments")?;
|
||||
|
||||
let deployments = find_collect(&legacy_db.deployments, None, None)
|
||||
.await
|
||||
.context("failed to get legacy deployments")?;
|
||||
@@ -121,6 +150,10 @@ pub async fn migrate_deployments(
|
||||
let mut permissions = Vec::<Permission>::new();
|
||||
|
||||
for deployment in deployments {
|
||||
if existing.iter().any(|d| d.name == deployment.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (user_id, level) in &deployment.permissions {
|
||||
let permission = Permission {
|
||||
id: Default::default(),
|
||||
@@ -148,6 +181,9 @@ pub async fn migrate_deployments(
|
||||
target_db
|
||||
.deployments
|
||||
.insert_many(new_deployments)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert deployments on target")?;
|
||||
}
|
||||
@@ -156,6 +192,9 @@ pub async fn migrate_deployments(
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert deployment permissions on target")?;
|
||||
}
|
||||
@@ -169,6 +208,10 @@ pub async fn migrate_builds(
|
||||
legacy_db: &v0::DbClient,
|
||||
target_db: &crate::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let existing = find_collect(&target_db.builds, None, None)
|
||||
.await
|
||||
.context("failed to get existing target builds")?;
|
||||
|
||||
let builds = find_collect(&legacy_db.builds, None, None)
|
||||
.await
|
||||
.context("failed to get legacy builds")?;
|
||||
@@ -177,6 +220,10 @@ pub async fn migrate_builds(
|
||||
let mut permissions = Vec::<Permission>::new();
|
||||
|
||||
for build in builds {
|
||||
if existing.iter().any(|b| b.name == build.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (user_id, level) in &build.permissions {
|
||||
let permission = Permission {
|
||||
id: Default::default(),
|
||||
@@ -202,16 +249,28 @@ pub async fn migrate_builds(
|
||||
target_db
|
||||
.builds
|
||||
.insert_many(new_builds)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert builds on target")?;
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to insert builds on target | {e}")
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
if !permissions.is_empty() {
|
||||
target_db
|
||||
.permissions
|
||||
.insert_many(permissions)
|
||||
.with_options(
|
||||
InsertManyOptions::builder().ordered(false).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to insert build permissions on target")?;
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to insert build permissions on target | {e}")
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
info!("builds have been migrated\n");
|
||||
@@ -240,6 +299,7 @@ pub async fn migrate_updates(
|
||||
target_db
|
||||
.updates
|
||||
.insert_many(updates)
|
||||
.with_options(InsertManyOptions::builder().ordered(false).build())
|
||||
.await
|
||||
.context("failed to insert updates on target")?;
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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_11;
|
||||
|
||||
pub async fn migrate_all_in_place(
|
||||
db: &v1_11::DbClient,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_builds_in_place(db).await?;
|
||||
migrate_deployments_in_place(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_builds_in_place(
|
||||
db: &v1_11::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)? },
|
||||
)
|
||||
.await
|
||||
.context("failed to insert builds on target")?;
|
||||
}
|
||||
|
||||
info!("builds have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_deployments_in_place(
|
||||
db: &v1_11::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)? },
|
||||
)
|
||||
.await
|
||||
.context("failed to insert deployments on target")?;
|
||||
}
|
||||
|
||||
info!("deployments have been migrated\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,14 +2,15 @@ use anyhow::{anyhow, Context};
|
||||
use command::run_monitor_command;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::entities::{
|
||||
build::{ImageRegistry, StandardRegistryConfig},
|
||||
deployment::{
|
||||
ContainerSummary, Conversion, Deployment, DeploymentConfig,
|
||||
DeploymentImage, DockerContainerStats, RestartMode,
|
||||
TerminationSignal,
|
||||
extract_registry_domain, ContainerSummary, Conversion,
|
||||
Deployment, DeploymentConfig, DeploymentImage,
|
||||
DockerContainerStats, RestartMode, TerminationSignal,
|
||||
},
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, SearchCombinator,
|
||||
EnvironmentVar, NoData, SearchCombinator,
|
||||
};
|
||||
use periphery_client::api::container::*;
|
||||
use resolver_api::Resolve;
|
||||
@@ -259,21 +260,6 @@ impl Resolve<Deploy> for State {
|
||||
}: Deploy,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
if let Err(e) = docker_login(
|
||||
&deployment.config.image_registry,
|
||||
registry_token.as_deref(),
|
||||
aws_ecr.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Ok(Log::error(
|
||||
"docker login",
|
||||
format_serror(
|
||||
&e.context("failed to login to docker registry").into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let image = if let DeploymentImage::Image { image } =
|
||||
&deployment.config.image
|
||||
{
|
||||
@@ -291,6 +277,33 @@ impl Resolve<Deploy> for State {
|
||||
));
|
||||
};
|
||||
|
||||
let image_registry = if aws_ecr.is_some() {
|
||||
ImageRegistry::AwsEcr(String::new())
|
||||
} else if deployment.config.image_registry_account.is_empty() {
|
||||
ImageRegistry::None(NoData {})
|
||||
} else {
|
||||
ImageRegistry::Standard(StandardRegistryConfig {
|
||||
account: deployment.config.image_registry_account.clone(),
|
||||
domain: extract_registry_domain(image)?,
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
|
||||
if let Err(e) = docker_login(
|
||||
&image_registry,
|
||||
registry_token.as_deref(),
|
||||
aws_ecr.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Ok(Log::error(
|
||||
"docker login",
|
||||
format_serror(
|
||||
&e.context("failed to login to docker registry").into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let _ = pull_image(image).await;
|
||||
debug!("image pulled");
|
||||
let _ = State
|
||||
|
||||
@@ -8,7 +8,7 @@ use periphery_client::api::git::{
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config, helpers::get_github_token, State,
|
||||
config::periphery_config, helpers::get_git_token, State,
|
||||
};
|
||||
|
||||
impl Resolve<GetLatestCommit, ()> for State {
|
||||
@@ -31,22 +31,29 @@ impl Resolve<CloneRepo> for State {
|
||||
#[instrument(name = "CloneRepo", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CloneRepo { args, github_token }: CloneRepo,
|
||||
CloneRepo { args, git_token }: CloneRepo,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
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",
|
||||
let CloneArgs {
|
||||
provider, account, ..
|
||||
} = &args;
|
||||
let token = match (account, provider, git_token) {
|
||||
(None, _, _) => None,
|
||||
(Some(_), None, _) => {
|
||||
return Err(anyhow!(
|
||||
"got incoming git account but no git provider"
|
||||
))
|
||||
}
|
||||
(Some(_), Some(_), Some(token)) => Some(token),
|
||||
(Some(account), Some(provider), None) => Some(
|
||||
get_git_token(provider, account)
|
||||
.with_context(
|
||||
|| format!("failed to get git token from periphery config | provider: {provider} | account: {account}")
|
||||
)?
|
||||
.clone(),
|
||||
),
|
||||
};
|
||||
git::clone(args, &periphery_config().repo_dir, github_token).await
|
||||
git::clone(args, &periphery_config().repo_dir, token).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{update::Log, SystemCommand};
|
||||
use monitor_client::{
|
||||
api::read::ListGitProviders,
|
||||
entities::{update::Log, SystemCommand},
|
||||
};
|
||||
use periphery_client::api::{
|
||||
build::*, container::*, git::*, network::*, stats::*, GetAccounts,
|
||||
GetHealth, GetSecrets, GetVersion, GetVersionResponse, PruneSystem,
|
||||
RunCommand,
|
||||
build::*, container::*, git::*, network::*, stats::*, GetHealth,
|
||||
GetVersion, GetVersionResponse, ListDockerRegistries, ListSecrets,
|
||||
PruneSystem, RunCommand,
|
||||
};
|
||||
use resolver_api::{derive::Resolver, Resolve, ResolveToString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{accounts_response, secrets_response},
|
||||
config::{
|
||||
docker_registries_response, git_providers_response,
|
||||
secrets_response,
|
||||
},
|
||||
State,
|
||||
};
|
||||
|
||||
@@ -31,9 +37,11 @@ pub enum PeripheryRequest {
|
||||
|
||||
// Config
|
||||
#[to_string_resolver]
|
||||
GetAccounts(GetAccounts),
|
||||
ListGitProviders(ListGitProviders),
|
||||
#[to_string_resolver]
|
||||
GetSecrets(GetSecrets),
|
||||
ListDockerRegistries(ListDockerRegistries),
|
||||
#[to_string_resolver]
|
||||
ListSecrets(ListSecrets),
|
||||
|
||||
// Stats / Info
|
||||
#[to_string_resolver]
|
||||
@@ -101,24 +109,43 @@ impl Resolve<GetVersion> for State {
|
||||
|
||||
//
|
||||
|
||||
impl ResolveToString<GetAccounts> for State {
|
||||
#[instrument(name = "GetAccounts", level = "debug", skip(self))]
|
||||
impl ResolveToString<ListGitProviders> for State {
|
||||
#[instrument(
|
||||
name = "ListGitProviders",
|
||||
level = "debug",
|
||||
skip(self)
|
||||
)]
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
_: GetAccounts,
|
||||
_: ListGitProviders,
|
||||
_: (),
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(accounts_response().clone())
|
||||
Ok(git_providers_response().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveToString<ListDockerRegistries> for State {
|
||||
#[instrument(
|
||||
name = "ListDockerRegistries",
|
||||
level = "debug",
|
||||
skip(self)
|
||||
)]
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
_: ListDockerRegistries,
|
||||
_: (),
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(docker_registries_response().clone())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl ResolveToString<GetSecrets> for State {
|
||||
#[instrument(name = "GetSecrets", level = "debug", skip(self))]
|
||||
impl ResolveToString<ListSecrets> for State {
|
||||
#[instrument(name = "ListSecrets", level = "debug", skip(self))]
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
_: GetSecrets,
|
||||
_: ListSecrets,
|
||||
_: (),
|
||||
) -> anyhow::Result<String> {
|
||||
Ok(secrets_response().clone())
|
||||
|
||||
@@ -6,7 +6,6 @@ use monitor_client::entities::{
|
||||
config::periphery::{CliArgs, Env, PeripheryConfig},
|
||||
logger::LogLevel,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub fn periphery_config() -> &'static PeripheryConfig {
|
||||
static PERIPHERY_CONFIG: OnceLock<PeripheryConfig> =
|
||||
@@ -57,12 +56,21 @@ pub fn periphery_config() -> &'static PeripheryConfig {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn accounts_response() -> &'static String {
|
||||
static ACCOUNTS_RESPONSE: OnceLock<String> = OnceLock::new();
|
||||
ACCOUNTS_RESPONSE.get_or_init(|| json!({
|
||||
"docker": periphery_config().docker_accounts.keys().collect::<Vec<_>>(),
|
||||
"github": periphery_config().github_accounts.keys().collect::<Vec<_>>(),
|
||||
}).to_string())
|
||||
pub fn git_providers_response() -> &'static String {
|
||||
static GIT_PROVIDERS_RESPONSE: OnceLock<String> = OnceLock::new();
|
||||
GIT_PROVIDERS_RESPONSE.get_or_init(|| {
|
||||
let config = periphery_config();
|
||||
serde_json::to_string(&config.git_providers).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn docker_registries_response() -> &'static String {
|
||||
static DOCKER_REGISTRIES_RESPONSE: OnceLock<String> =
|
||||
OnceLock::new();
|
||||
DOCKER_REGISTRIES_RESPONSE.get_or_init(|| {
|
||||
let config = periphery_config();
|
||||
serde_json::to_string(&config.docker_registries).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn secrets_response() -> &'static String {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
build::{ImageRegistry, StandardRegistryConfig},
|
||||
config::core::AwsEcrConfig,
|
||||
EnvironmentVar,
|
||||
};
|
||||
@@ -8,22 +8,29 @@ use run_command::async_run_command;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
pub fn get_github_token(
|
||||
github_account: &String,
|
||||
pub fn get_git_token(
|
||||
domain: &str,
|
||||
account_username: &str,
|
||||
) -> 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}"))
|
||||
.git_providers
|
||||
.iter()
|
||||
.find(|_provider| _provider.domain == domain)
|
||||
.and_then(|provider| provider.accounts
|
||||
.iter()
|
||||
.find(|account| account.username == account_username).map(|account| &account.token))
|
||||
.with_context(|| format!("did not find token in config for git account {account_username} | domain {domain}"))
|
||||
}
|
||||
|
||||
pub fn get_docker_token(
|
||||
docker_account: &String,
|
||||
domain: &str,
|
||||
account_username: &str,
|
||||
) -> 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}"))
|
||||
.docker_registries
|
||||
.iter().find(|registry| registry.domain == domain)
|
||||
.and_then(|registry| registry.accounts.iter().find(|account| account.username == account_username).map(|account| &account.token))
|
||||
.with_context(|| format!("did not find token in config for docker account {account_username} | domain {domain}"))
|
||||
}
|
||||
|
||||
pub fn parse_extra_args(extra_args: &[String]) -> String {
|
||||
@@ -52,76 +59,57 @@ pub async fn docker_login(
|
||||
// For local config override from core.
|
||||
aws_ecr: Option<&AwsEcrConfig>,
|
||||
) -> 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)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"docker login -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"dockerhub login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
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)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"docker login ghcr.io -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"ghcr login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
let (domain, account) = match registry {
|
||||
// Early return for no login
|
||||
ImageRegistry::None(_) => return Ok(false),
|
||||
// Early return because Ecr is different
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
let AwsEcrConfig { region, account_id } = aws_ecr
|
||||
.with_context(|| {
|
||||
format!("Could not find aws ecr config for label {label}")
|
||||
if label.is_empty() {
|
||||
String::from("Could not find aws ecr config")
|
||||
} else {
|
||||
format!("Could not find aws ecr config for label {label}")
|
||||
}
|
||||
})?;
|
||||
let registry_token = registry_token
|
||||
.context("aws ecr build missing registry token from core")?;
|
||||
let log = async_run_command(&format!("docker login {account_id}.dkr.ecr.{region}.amazonaws.com -u AWS -p {registry_token}")).await;
|
||||
let command = format!("docker login {account_id}.dkr.ecr.{region}.amazonaws.com -u AWS -p {registry_token}");
|
||||
let log = async_run_command(&command).await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
return Ok(true);
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
return Err(anyhow!(
|
||||
"aws ecr login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
ImageRegistry::Standard(StandardRegistryConfig {
|
||||
domain,
|
||||
account,
|
||||
..
|
||||
}) => (domain.as_str(), account),
|
||||
};
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!("Must configure account for registry domain {domain}, got empty string"));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_docker_token(domain, account)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"docker login {domain} -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"{domain} login error: stdout: {} | stderr: {}",
|
||||
log.stdout,
|
||||
log.stderr
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,8 @@ impl GetBuildMonthlyStatsResponse {
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetBuildVersionsResponse)]
|
||||
pub struct GetBuildVersions {
|
||||
#[response(ListBuildVersionsResponse)]
|
||||
pub struct ListBuildVersions {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub build: String,
|
||||
@@ -201,7 +201,7 @@ pub struct GetBuildVersions {
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetBuildVersionsResponse = Vec<BuildVersionResponseItem>;
|
||||
pub type ListBuildVersionsResponse = Vec<BuildVersionResponseItem>;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
@@ -212,36 +212,6 @@ pub struct BuildVersionResponseItem {
|
||||
|
||||
//
|
||||
|
||||
/// List the available github organizations which can be attached to builds.
|
||||
/// Response: [ListGithubOrganizationsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListGithubOrganizationsResponse)]
|
||||
pub struct ListGithubOrganizations {}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListGithubOrganizationsResponse = Vec<String>;
|
||||
|
||||
//
|
||||
|
||||
/// List the available docker organizations which can be attached to builds.
|
||||
/// Response: [ListDockerOrganizationsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListDockerOrganizationsResponse)]
|
||||
pub struct ListDockerOrganizations {}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListDockerOrganizationsResponse = Vec<String>;
|
||||
|
||||
//
|
||||
|
||||
/// Gets a list of existing values used as extra args across other builds.
|
||||
/// Useful to offer suggestions. Response: [ListCommonBuildExtraArgsResponse]
|
||||
#[typeshare]
|
||||
@@ -258,3 +228,29 @@ pub struct ListCommonBuildExtraArgs {
|
||||
|
||||
#[typeshare]
|
||||
pub type ListCommonBuildExtraArgsResponse = Vec<String>;
|
||||
|
||||
//
|
||||
|
||||
/// Get whether a Build's target repo has a webhook for the build configured. Response: [GetBuildWebhookEnabledResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetBuildWebhookEnabledResponse)]
|
||||
pub struct GetBuildWebhookEnabled {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub build: String,
|
||||
}
|
||||
|
||||
/// Response for [GetBuildWebhookEnabled]
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetBuildWebhookEnabledResponse {
|
||||
/// Whether the repo webhooks can even be managed.
|
||||
/// The repo owner must be in `github_webhook_app.owners` list to be managed.
|
||||
pub managed: bool,
|
||||
/// Whether pushes to branch trigger build. Will always be false if managed is false.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
@@ -80,29 +80,3 @@ pub struct GetBuildersSummaryResponse {
|
||||
/// The total number of builders.
|
||||
pub total: u32,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get the docker / github accounts which are available for use on the builder.
|
||||
/// Response: [GetBuilderAvailableAccountsResponse].
|
||||
///
|
||||
/// Note. Builds using this builder can only use the docker / github accounts available in this response.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetBuilderAvailableAccountsResponse)]
|
||||
pub struct GetBuilderAvailableAccounts {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub builder: String,
|
||||
}
|
||||
|
||||
/// Response for [GetBuilderAvailableAccounts].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetBuilderAvailableAccountsResponse {
|
||||
pub github: Vec<String>,
|
||||
pub docker: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ pub use user::*;
|
||||
pub use user_group::*;
|
||||
pub use variable::*;
|
||||
|
||||
use crate::entities::Timelength;
|
||||
use crate::entities::{
|
||||
config::{DockerRegistry, GitProvider},
|
||||
update::ResourceTarget,
|
||||
Timelength,
|
||||
};
|
||||
|
||||
pub trait MonitorReadRequest: HasResponse {}
|
||||
|
||||
@@ -67,7 +71,7 @@ pub struct GetVersionResponse {
|
||||
|
||||
//
|
||||
|
||||
/// Get info about the core api.
|
||||
/// Get info about the core api configuration.
|
||||
/// Response: [GetCoreInfoResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
@@ -85,25 +89,94 @@ pub struct GetCoreInfoResponse {
|
||||
pub title: String,
|
||||
/// The monitoring interval of this core api.
|
||||
pub monitoring_interval: Timelength,
|
||||
/// The github webhook base url to use with github webhooks.
|
||||
pub github_webhook_base_url: String,
|
||||
/// The webhook base url.
|
||||
pub webhook_base_url: String,
|
||||
/// Whether transparent mode is enabled, which gives all users read access to all resources.
|
||||
pub transparent_mode: bool,
|
||||
/// Whether UI write access should be disabled
|
||||
pub ui_write_disabled: bool,
|
||||
/// The repo owners for which github webhook management api is available
|
||||
pub github_webhook_owners: Vec<String>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get the available aws ecr config labels from the core config.
|
||||
/// Response: [GetAvailableAwsEcrLabelsResponse].
|
||||
/// List the git providers.
|
||||
/// Response: [ListGitProvidersResponse].
|
||||
///
|
||||
/// Includes:
|
||||
/// - providers in core config
|
||||
/// - providers configured on builds, repos, syncs
|
||||
/// - providers on the optional Server or Builder
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetAvailableAwsEcrLabelsResponse)]
|
||||
pub struct GetAvailableAwsEcrLabels {}
|
||||
#[response(ListGitProvidersResponse)]
|
||||
pub struct ListGitProviders {
|
||||
/// Accepts an optional Server or Builder target to expand the core list with
|
||||
/// providers available on that specific resource.
|
||||
pub target: Option<ResourceTarget>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetAvailableAwsEcrLabelsResponse = Vec<String>;
|
||||
pub type ListGitProvidersResponse = Vec<GitProvider>;
|
||||
|
||||
//
|
||||
|
||||
/// List the suggested docker registry providers.
|
||||
/// Response: [ListDockerRegistriesResponse].
|
||||
///
|
||||
/// Includes:
|
||||
/// - registries in core config
|
||||
/// - registries configured on builds, deployments
|
||||
/// - registries on the optional Server or Builder
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListDockerRegistriesResponse)]
|
||||
pub struct ListDockerRegistries {
|
||||
/// Accepts an optional Server or Builder target to expand the core list with
|
||||
/// providers available on that specific resource.
|
||||
pub target: Option<ResourceTarget>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListDockerRegistriesResponse = Vec<DockerRegistry>;
|
||||
|
||||
//
|
||||
|
||||
/// List the available aws ecr config labels from the core config.
|
||||
/// Response: [ListAwsEcrLabelsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListAwsEcrLabelsResponse)]
|
||||
pub struct ListAwsEcrLabels {}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListAwsEcrLabelsResponse = Vec<String>;
|
||||
|
||||
//
|
||||
|
||||
/// List the available secrets from the core config.
|
||||
/// Response: [ListSecretsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListSecretsResponse)]
|
||||
pub struct ListSecrets {
|
||||
/// Accepts an optional Server or Builder target to expand the core list with
|
||||
/// providers available on that specific resource.
|
||||
pub target: Option<ResourceTarget>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListSecretsResponse = Vec<String>;
|
||||
|
||||
@@ -108,5 +108,3 @@ pub struct GetProceduresSummaryResponse {
|
||||
/// The number of procedures with unknown state.
|
||||
pub unknown: u32,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -110,3 +110,31 @@ pub struct GetReposSummaryResponse {
|
||||
/// The number of repos with unknown state.
|
||||
pub unknown: u32,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get a target Repo's configured webhooks. Response: [GetRepoWebhooksEnabledResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetRepoWebhooksEnabledResponse)]
|
||||
pub struct GetRepoWebhooksEnabled {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub repo: String,
|
||||
}
|
||||
|
||||
/// Response for [GetRepoWebhooksEnabled]
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetRepoWebhooksEnabledResponse {
|
||||
/// Whether the repo webhooks can even be managed.
|
||||
/// The repo owner must be in `github_webhook_app.owners` list to be managed.
|
||||
pub managed: bool,
|
||||
/// Whether pushes to branch trigger clone. Will always be false if managed is false.
|
||||
pub clone_enabled: bool,
|
||||
/// Whether pushes to branch trigger pull. Will always be false if managed is false.
|
||||
pub pull_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -316,51 +316,3 @@ pub struct GetServersSummaryResponse {
|
||||
/// The number of disabled servers.
|
||||
pub disabled: I64,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get the usernames for the available github / docker accounts
|
||||
/// on the target server, or only available globally if no server
|
||||
/// is provided.
|
||||
///
|
||||
/// Response: [GetAvailableAccountsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetAvailableAccountsResponse)]
|
||||
pub struct GetAvailableAccounts {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub server: Option<String>,
|
||||
}
|
||||
|
||||
/// Response for [GetAvailableAccounts].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetAvailableAccountsResponse {
|
||||
/// The github usernames
|
||||
pub github: Vec<String>,
|
||||
/// The docker usernames.
|
||||
pub docker: Vec<String>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get the keys for available secrets on the target server.
|
||||
/// Response: [GetAvailableSecretsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetAvailableSecretsResponse)]
|
||||
pub struct GetAvailableSecrets {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub server: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetAvailableSecretsResponse = Vec<String>;
|
||||
|
||||
@@ -111,3 +111,31 @@ pub struct GetResourceSyncsSummaryResponse {
|
||||
/// The number of syncs with unknown state.
|
||||
pub unknown: u32,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Get a target Sync's configured webhooks. Response: [GetSyncWebhooksEnabledResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(GetSyncWebhooksEnabledResponse)]
|
||||
pub struct GetSyncWebhooksEnabled {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub sync: String,
|
||||
}
|
||||
|
||||
/// Response for [GetSyncWebhooksEnabled]
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetSyncWebhooksEnabledResponse {
|
||||
/// Whether the repo webhooks can even be managed.
|
||||
/// The repo owner must be in `github_webhook_app.owners` list to be managed.
|
||||
pub managed: bool,
|
||||
/// Whether pushes to branch trigger refresh. Will always be false if managed is false.
|
||||
pub refresh_enabled: bool,
|
||||
/// Whether pushes to branch trigger sync execution. Will always be false if managed is false.
|
||||
pub sync_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ pub type ListApiKeysResponse = Vec<ApiKey>;
|
||||
|
||||
//
|
||||
|
||||
/// Gets list of api keys for the user at ID.
|
||||
/// **Admin only.**
|
||||
/// Gets list of api keys for the user.
|
||||
/// Will still fail if you call for a user_id that isn't a service user.
|
||||
/// Response: [ListApiKeysForServiceUserResponse]
|
||||
#[typeshare]
|
||||
@@ -33,8 +33,9 @@ pub type ListApiKeysResponse = Vec<ApiKey>;
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListApiKeysForServiceUserResponse)]
|
||||
pub struct ListApiKeysForServiceUser {
|
||||
/// The id of the user.
|
||||
pub user_id: String,
|
||||
/// Id or username
|
||||
#[serde(alias = "id", alias = "username")]
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
@@ -42,8 +43,28 @@ pub type ListApiKeysForServiceUserResponse = Vec<ApiKey>;
|
||||
|
||||
//
|
||||
|
||||
/// Gets list of monitor users.
|
||||
/// **Admin only.**
|
||||
/// Find a user.
|
||||
/// Response: [FindUserResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(FindUserResponse)]
|
||||
pub struct FindUser {
|
||||
/// Id or username
|
||||
#[serde(alias = "id", alias = "username")]
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type FindUserResponse = User;
|
||||
|
||||
//
|
||||
|
||||
/// **Admin only.**
|
||||
/// Gets list of monitor users.
|
||||
/// Response: [ListUsersResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
|
||||
@@ -35,12 +35,5 @@ pub type GetVariableResponse = Variable;
|
||||
#[response(ListVariablesResponse)]
|
||||
pub struct ListVariables {}
|
||||
|
||||
/// The response of [ListVariables].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ListVariablesResponse {
|
||||
/// The available global variables.
|
||||
pub variables: Vec<Variable>,
|
||||
/// The available global secret keys
|
||||
pub secrets: Vec<String>,
|
||||
}
|
||||
pub type ListVariablesResponse = Vec<Variable>;
|
||||
|
||||
@@ -3,7 +3,10 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::build::{Build, _PartialBuildConfig};
|
||||
use crate::entities::{
|
||||
build::{Build, _PartialBuildConfig},
|
||||
NoData,
|
||||
};
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
|
||||
@@ -77,3 +80,41 @@ pub struct UpdateBuild {
|
||||
/// The partial config update to apply.
|
||||
pub config: _PartialBuildConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Create a webhook on the github repo attached to the build
|
||||
/// passed in request. Response: [CreateBuildWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(CreateBuildWebhookResponse)]
|
||||
pub struct CreateBuildWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub build: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type CreateBuildWebhookResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Delete a webhook on the github repo attached to the build
|
||||
/// passed in request. Response: [CreateBuildWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(DeleteBuildWebhookResponse)]
|
||||
pub struct DeleteBuildWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub build: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteBuildWebhookResponse = NoData;
|
||||
|
||||
@@ -5,13 +5,13 @@ use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
permission::{PermissionLevel, UserTarget},
|
||||
update::ResourceTarget,
|
||||
update::{ResourceTarget, ResourceTargetVariant},
|
||||
NoData,
|
||||
};
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
|
||||
/// Update a user or user groups permission on a resource.
|
||||
/// **Admin only.** Update a user or user groups permission on a resource.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
@@ -33,7 +33,29 @@ pub type UpdatePermissionOnTargetResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Update a user's "base" permissions, eg. "enabled".
|
||||
/// **Admin only.** Update a user or user groups base permission level on a resource type.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(UpdatePermissionOnResourceTypeResponse)]
|
||||
pub struct UpdatePermissionOnResourceType {
|
||||
/// Specify the user or user group.
|
||||
pub user_target: UserTarget,
|
||||
/// The resource type: eg. Server, Build, Deployment, etc.
|
||||
pub resource_type: ResourceTargetVariant,
|
||||
/// The base permission level.
|
||||
pub permission: PermissionLevel,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UpdatePermissionOnResourceTypeResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// **Admin only.** Update a user's "base" permissions, eg. "enabled".
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
|
||||
@@ -3,7 +3,10 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::repo::{Repo, _PartialRepoConfig};
|
||||
use crate::entities::{
|
||||
repo::{Repo, _PartialRepoConfig},
|
||||
NoData,
|
||||
};
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
|
||||
@@ -80,3 +83,52 @@ pub struct UpdateRepo {
|
||||
/// The partial config update to apply.
|
||||
pub config: _PartialRepoConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RepoWebhookAction {
|
||||
Clone,
|
||||
Pull,
|
||||
}
|
||||
|
||||
/// Create a webhook on the github repo attached to the (monitor) repo
|
||||
/// passed in request. Response: [CreateRepoWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(CreateRepoWebhookResponse)]
|
||||
pub struct CreateRepoWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub repo: String,
|
||||
/// "Clone" or "Pull"
|
||||
pub action: RepoWebhookAction,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type CreateRepoWebhookResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Delete the webhook on the github repo attached to the (monitor) repo
|
||||
/// passed in request. Response: [DeleteRepoWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(DeleteRepoWebhookResponse)]
|
||||
pub struct DeleteRepoWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub repo: String,
|
||||
/// "Clone" or "Pull"
|
||||
pub action: RepoWebhookAction,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteRepoWebhookResponse = NoData;
|
||||
|
||||
@@ -3,8 +3,9 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::sync::{
|
||||
ResourceSync, _PartialResourceSyncConfig,
|
||||
use crate::entities::{
|
||||
sync::{ResourceSync, _PartialResourceSyncConfig},
|
||||
NoData,
|
||||
};
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
@@ -96,3 +97,52 @@ pub struct RefreshResourceSyncPending {
|
||||
/// Id or name
|
||||
pub sync: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum SyncWebhookAction {
|
||||
Refresh,
|
||||
Sync,
|
||||
}
|
||||
|
||||
/// Create a webhook on the github repo attached to the sync
|
||||
/// passed in request. Response: [CreateSyncWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(CreateSyncWebhookResponse)]
|
||||
pub struct CreateSyncWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub sync: String,
|
||||
/// "Refresh" or "Sync"
|
||||
pub action: SyncWebhookAction,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type CreateSyncWebhookResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Delete the webhook on the github repo attached to the sync
|
||||
/// passed in request. Response: [DeleteSyncWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(DeleteSyncWebhookResponse)]
|
||||
pub struct DeleteSyncWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub sync: String,
|
||||
/// "Refresh" or "Sync"
|
||||
pub action: SyncWebhookAction,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteSyncWebhookResponse = NoData;
|
||||
|
||||
@@ -7,11 +7,8 @@ use typeshare::typeshare;
|
||||
use crate::entities::{MongoId, I64};
|
||||
|
||||
use super::{
|
||||
_Serror,
|
||||
deployment::DeploymentState,
|
||||
server::stats::SeverityLevel,
|
||||
update::{Log, ResourceTarget},
|
||||
Version,
|
||||
_Serror, deployment::DeploymentState, server::stats::SeverityLevel,
|
||||
update::ResourceTarget, Version,
|
||||
};
|
||||
|
||||
/// Representation of an alert in the system.
|
||||
@@ -170,8 +167,6 @@ pub enum AlertData {
|
||||
name: String,
|
||||
/// The version that failed to build
|
||||
version: Version,
|
||||
/// The reason build failed
|
||||
err: Option<Log>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ pub struct BuildListItemInfo {
|
||||
pub last_built_at: I64,
|
||||
/// The current version of the build
|
||||
pub version: Version,
|
||||
/// The Github repo used as the source of the build
|
||||
/// The git provider domain
|
||||
pub git_provider: String,
|
||||
/// The repo used as the source of the build
|
||||
pub repo: String,
|
||||
/// The branch of the repo
|
||||
pub branch: String,
|
||||
@@ -76,7 +78,21 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub version: Version,
|
||||
|
||||
/// The Github repo used as the source of the build.
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
#[partial_default(default_git_provider())]
|
||||
pub git_provider: String,
|
||||
|
||||
/// Whether to use https to clone the repo (versus http). Default: true
|
||||
///
|
||||
/// Note. Monitor does not currently support cloning repos via ssh.
|
||||
#[serde(default = "default_git_https")]
|
||||
#[builder(default = "default_git_https()")]
|
||||
#[partial_default(default_git_https())]
|
||||
pub git_https: bool,
|
||||
|
||||
/// The repo used as the source of the build.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub repo: String,
|
||||
@@ -92,11 +108,14 @@ pub struct BuildConfig {
|
||||
#[builder(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)]
|
||||
/// The git account used to access private repos.
|
||||
/// Passing empty string can only clone public repos.
|
||||
///
|
||||
/// Note. A token for the account must be available in the core config or the builder server's periphery config
|
||||
/// for the configured git provider.
|
||||
#[serde(default, alias = "github_account")]
|
||||
#[builder(default)]
|
||||
pub github_account: String,
|
||||
pub git_account: String,
|
||||
|
||||
/// The optional command run after repo clone and before docker build.
|
||||
#[serde(default)]
|
||||
@@ -196,6 +215,14 @@ impl BuildConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_git_provider() -> String {
|
||||
String::from("github.com")
|
||||
}
|
||||
|
||||
fn default_git_https() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_branch() -> String {
|
||||
String::from("main")
|
||||
}
|
||||
@@ -218,10 +245,12 @@ impl Default for BuildConfig {
|
||||
builder_id: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
version: Default::default(),
|
||||
git_provider: default_git_provider(),
|
||||
git_https: default_git_https(),
|
||||
repo: Default::default(),
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
git_account: Default::default(),
|
||||
pre_build: Default::default(),
|
||||
build_path: default_build_path(),
|
||||
dockerfile_path: default_dockerfile_path(),
|
||||
@@ -243,19 +272,12 @@ impl Default for BuildConfig {
|
||||
pub enum ImageRegistry {
|
||||
/// Don't push the image to any registry
|
||||
None(NoData),
|
||||
/// Push the image to DockerHub
|
||||
DockerHub(CloudRegistryConfig),
|
||||
/// Push the image to the Github Container Registry.
|
||||
///
|
||||
/// See [the Github docs](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images)
|
||||
/// for information on creating an access token
|
||||
Ghcr(CloudRegistryConfig),
|
||||
/// Push the image to a standard image registry (any domain)
|
||||
Standard(StandardRegistryConfig),
|
||||
/// Push the image to Aws Elastic Container Registry
|
||||
///
|
||||
/// The string held in 'params' should match a label of an `aws_ecr_registry` in the core config.
|
||||
AwsEcr(String),
|
||||
/// Todo. Will point to a custom "Registry" resource by id
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Default for ImageRegistry {
|
||||
@@ -264,13 +286,17 @@ impl Default for ImageRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for a cloud image registry, like account and organization.
|
||||
/// Configuration for a standard image registry
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct CloudRegistryConfig {
|
||||
/// Specify an account to use with the cloud registry.
|
||||
pub struct StandardRegistryConfig {
|
||||
/// Specify the registry provider domain. Default: `docker.io`
|
||||
#[serde(default = "default_registry_domain")]
|
||||
pub domain: String,
|
||||
|
||||
/// Specify an account to use with the registry.
|
||||
#[serde(default)]
|
||||
pub account: String,
|
||||
|
||||
@@ -280,6 +306,10 @@ pub struct CloudRegistryConfig {
|
||||
pub organization: String,
|
||||
}
|
||||
|
||||
fn default_registry_domain() -> String {
|
||||
String::from("docker.io")
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct BuildActionState {
|
||||
|
||||
@@ -6,6 +6,7 @@ use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use super::{
|
||||
config::{DockerRegistry, GitProvider},
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
MergePartial,
|
||||
};
|
||||
@@ -38,6 +39,7 @@ pub struct BuilderListItemInfo {
|
||||
EnumString
|
||||
)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum BuilderConfig {
|
||||
/// Use a connected server an image builder.
|
||||
Server(ServerBuilderConfig),
|
||||
@@ -65,6 +67,7 @@ impl Default for BuilderConfig {
|
||||
EnumString
|
||||
)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum PartialBuilderConfig {
|
||||
Server(_PartialServerBuilderConfig),
|
||||
Aws(_PartialAwsBuilderConfig),
|
||||
@@ -224,12 +227,13 @@ impl MergePartial for BuilderConfig {
|
||||
.use_public_ip
|
||||
.unwrap_or(config.use_public_ip),
|
||||
port: partial.port.unwrap_or(config.port),
|
||||
github_accounts: partial
|
||||
.github_accounts
|
||||
.unwrap_or(config.github_accounts),
|
||||
docker_accounts: partial
|
||||
.docker_accounts
|
||||
.unwrap_or(config.docker_accounts),
|
||||
git_providers: partial
|
||||
.git_providers
|
||||
.unwrap_or(config.git_providers),
|
||||
docker_registries: partial
|
||||
.docker_registries
|
||||
.unwrap_or(config.docker_registries),
|
||||
secrets: partial.secrets.unwrap_or(config.secrets),
|
||||
};
|
||||
BuilderConfig::Aws(config)
|
||||
}
|
||||
@@ -308,12 +312,15 @@ pub struct AwsBuilderConfig {
|
||||
/// This should include a security group to allow core inbound access to the periphery port.
|
||||
pub security_group_ids: Vec<String>,
|
||||
|
||||
/// Which github accounts (usernames) are available on the AMI
|
||||
/// Which git providers are available on the AMI
|
||||
#[serde(default)]
|
||||
pub github_accounts: Vec<String>,
|
||||
/// Which dockerhub accounts (usernames) are available on the AMI
|
||||
pub git_providers: Vec<GitProvider>,
|
||||
/// Which docker registries are available on the AMI.
|
||||
#[serde(default)]
|
||||
pub docker_accounts: Vec<String>,
|
||||
pub docker_registries: Vec<DockerRegistry>,
|
||||
/// Which secrets are available on the AMI.
|
||||
#[serde(default)]
|
||||
pub secrets: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for AwsBuilderConfig {
|
||||
@@ -329,8 +336,9 @@ impl Default for AwsBuilderConfig {
|
||||
key_pair_name: Default::default(),
|
||||
assign_public_ip: false,
|
||||
use_public_ip: false,
|
||||
github_accounts: Default::default(),
|
||||
docker_accounts: Default::default(),
|
||||
git_providers: Default::default(),
|
||||
docker_registries: Default::default(),
|
||||
secrets: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::entities::{
|
||||
Timelength,
|
||||
};
|
||||
|
||||
use super::{DockerRegistry, GitProvider};
|
||||
|
||||
/// # Monitor Core Environment Variables
|
||||
///
|
||||
/// You can override any fields of the [CoreConfig] by passing the associated
|
||||
@@ -53,14 +55,10 @@ pub struct Env {
|
||||
pub monitor_keep_stats_for_days: Option<u64>,
|
||||
/// Override `keep_alerts_for_days`
|
||||
pub monitor_keep_alerts_for_days: Option<u64>,
|
||||
/// Override `github_webhook_secret`
|
||||
pub monitor_github_webhook_secret: Option<String>,
|
||||
/// Override `github_webhook_base_url`
|
||||
pub monitor_github_webhook_base_url: Option<String>,
|
||||
/// Override `github_organizations`
|
||||
pub monitor_github_organizations: Option<Vec<String>>,
|
||||
/// Override `docker_organizations`
|
||||
pub monitor_docker_organizations: Option<Vec<String>>,
|
||||
/// Override `webhook_secret`
|
||||
pub monitor_webhook_secret: Option<String>,
|
||||
/// Override `webhook_base_url`
|
||||
pub monitor_webhook_base_url: Option<String>,
|
||||
|
||||
/// Override `logging.level`
|
||||
pub monitor_logging_level: Option<LogLevel>,
|
||||
@@ -79,6 +77,13 @@ pub struct Env {
|
||||
/// Override `local_auth`
|
||||
pub monitor_local_auth: Option<bool>,
|
||||
|
||||
/// Override `google_oauth.enabled`
|
||||
pub monitor_google_oauth_enabled: Option<bool>,
|
||||
/// Override `google_oauth.id`
|
||||
pub monitor_google_oauth_id: Option<String>,
|
||||
/// Override `google_oauth.secret`
|
||||
pub monitor_google_oauth_secret: Option<String>,
|
||||
|
||||
/// Override `github_oauth.enabled`
|
||||
pub monitor_github_oauth_enabled: Option<bool>,
|
||||
/// Override `github_oauth.id`
|
||||
@@ -86,12 +91,19 @@ pub struct Env {
|
||||
/// Override `github_oauth.secret`
|
||||
pub monitor_github_oauth_secret: Option<String>,
|
||||
|
||||
/// Override `google_oauth.enabled`
|
||||
pub monitor_google_oauth_enabled: Option<bool>,
|
||||
/// Override `google_oauth.id`
|
||||
pub monitor_google_oauth_id: Option<String>,
|
||||
/// Override `google_oauth.secret`
|
||||
pub monitor_google_oauth_secret: Option<String>,
|
||||
/// Override `github_webhook_app.app_id`
|
||||
pub monitor_github_webhook_app_app_id: Option<i64>,
|
||||
/// Override `github_webhook_app.installations[i].id`. Accepts comma seperated list.
|
||||
///
|
||||
/// Note. Paired by index with values in `monitor_github_webhook_app_installations_namespaces`
|
||||
pub monitor_github_webhook_app_installations_ids: Option<Vec<i64>>,
|
||||
/// Override `github_webhook_app.installations[i].namespace`. Accepts comma seperated list.
|
||||
///
|
||||
/// Note. Paired by index with values in `monitor_github_webhook_app_installations_ids`
|
||||
pub monitor_github_webhook_app_installations_namespaces:
|
||||
Option<Vec<String>>,
|
||||
/// Override `github_webhook_app.pk_path`
|
||||
pub monitor_github_webhook_app_pk_path: Option<String>,
|
||||
|
||||
/// Override `mongo.uri`
|
||||
pub monitor_mongo_uri: Option<String>,
|
||||
@@ -136,64 +148,59 @@ fn default_config_path() -> String {
|
||||
/// ## default: 'Monitor'
|
||||
/// title = "Monitor"
|
||||
///
|
||||
/// ## required for oauth functionality. this should be the url used to access monitor in browser,
|
||||
/// ## Required for oauth functionality. This should be the url used to access monitor in browser,
|
||||
/// ## potentially behind DNS.
|
||||
/// ## eg https://monitor.dev or http://12.34.56.78:9000.
|
||||
/// ## this should match the address configured in your oauth app.
|
||||
/// ## no default
|
||||
/// ## Eg https://monitor.dev or http://12.34.56.78:9000.
|
||||
/// ## This should match the address configured in your oauth app.
|
||||
/// ## Required (no default).
|
||||
/// host = "https://monitor.dev"
|
||||
///
|
||||
/// ## the port the core system will run on. if running core in docker container,
|
||||
/// ## leave as this port as 9000 and use port bind eg. -p 9001:9000
|
||||
/// ## default: 9000
|
||||
/// ## The port the core system will run on. If running core in docker container,
|
||||
/// ## Leave as this port as 9000 and use port bind eg. -p 9001:9000
|
||||
/// ## Default: 9000
|
||||
/// port = 9000
|
||||
///
|
||||
/// ## required to match a passkey in periphery config.
|
||||
/// ## token used to authenticate core requests to periphery
|
||||
/// ## no default
|
||||
/// ## Must match a passkey in periphery config to communicate with periphery.
|
||||
/// ## Required (No default)
|
||||
/// passkey = "a_random_passkey"
|
||||
///
|
||||
/// ## specify the log level of the monitor core application
|
||||
/// ## default: info
|
||||
/// ## options: off, error, warn, info, debug, trace
|
||||
/// ## Specify the log level of the monitor core application.
|
||||
/// ## Default: `info`.
|
||||
/// ## Options: `off`, `error`, `warn`, `info`, `debug`, `trace`.
|
||||
/// logging.level = "info"
|
||||
///
|
||||
/// ## specify the logging format for stdout / stderr.
|
||||
/// ## default: standard
|
||||
/// ## options: standard, json, none
|
||||
/// ## Specify the logging format for stdout / stderr.
|
||||
/// ## Default: standard
|
||||
/// ## Options: `standard`, `json`, `none`
|
||||
/// logging.stdio = "standard"
|
||||
///
|
||||
/// ## specify a opentelemetry otlp endpoint to send traces to
|
||||
/// ## optional, default unassigned
|
||||
/// ## Specify a opentelemetry otlp endpoint to send traces to.
|
||||
/// ## Optional, default unassigned (don't export telemetry).
|
||||
/// # logging.otlp_endpoint = "http://localhost:4317"
|
||||
///
|
||||
/// ## specify how long an issued jwt stays valid.
|
||||
/// ## all jwts are invalidated on application restart.
|
||||
/// ## default: 1-day.
|
||||
/// ## options: 1-hr, 12-hr, 1-day, 3-day, 1-wk, 2-wk, 30-day
|
||||
/// ## Specify how long an issued jwt stays valid.
|
||||
/// ## All jwts are invalidated on application restart.
|
||||
/// ## Default: `1-day`.
|
||||
/// ## Options: `1-hr`, `12-hr`, `1-day`, `3-day`, `1-wk`, `2-wk`, `30-day`.
|
||||
/// jwt_valid_for = "1-day"
|
||||
///
|
||||
/// ## controls the granularity of the system stats collection by monitor core
|
||||
/// ## default: 15-sec
|
||||
/// ## options: 5-sec, 15-sec, 30-sec, 1-min, 2-min, 5-min, 15-min
|
||||
/// ## Controls the granularity of the system stats collection by monitor core.
|
||||
/// ## Options: `5-sec`, `15-sec`, `30-sec`, `1-min`, `2-min`, `5-min`.
|
||||
/// ## Default: `15-sec`.
|
||||
/// monitoring_interval = "15-sec"
|
||||
///
|
||||
/// ## number of days to keep stats around, or 0 to disable pruning.
|
||||
/// ## stats older than this number of days are deleted daily
|
||||
/// ## default: 0 (pruning disabled)
|
||||
/// keep_stats_for_days = 0
|
||||
/// ## Number of days to store stats, or 0 to disable stats pruning.
|
||||
/// ## Stats older than this number of days are deleted daily
|
||||
/// ## Default: 0 (pruning disabled)
|
||||
/// keep_stats_for_days = 14
|
||||
///
|
||||
/// ## token that has to be given to github during repo webhook config as the secret
|
||||
/// ## default: empty (none)
|
||||
/// github_webhook_secret = "your_random_webhook_secret"
|
||||
/// ## Number of days to store alerts, or 0 to disable alert pruning.
|
||||
/// ## Alerts older than this number of days are deleted daily
|
||||
/// ## Default: 0 (pruning disabled)
|
||||
/// keep_alerts_for_days = 14
|
||||
///
|
||||
/// ## an alternate base url that is used to recieve github webhook requests
|
||||
/// ## if empty or not specified, will use 'host' address as base
|
||||
/// ## default: empty (none)
|
||||
/// # github_webhook_base_url = "https://github-webhook.monitor.dev"
|
||||
///
|
||||
/// ## these will be used by the GUI to attach to builds.
|
||||
/// ## when attached to build, image will be pushed to repo under the specified organization.
|
||||
/// ## These will be available .
|
||||
/// ## When attached to build, image will be pushed to repo under the specified organization.
|
||||
/// ## if empty, the "docker organization" config option will not be shown.
|
||||
/// ## default: empty
|
||||
/// # docker_organizations = ["your_docker_org1", "your_docker_org_2"]
|
||||
@@ -210,15 +217,36 @@ fn default_config_path() -> String {
|
||||
/// ## default: false
|
||||
/// # local_auth = true
|
||||
///
|
||||
/// ## Use to configure google oauth
|
||||
/// # google_oauth.enabled = true
|
||||
/// # google_oauth.id = "your_google_client_id"
|
||||
/// # google_oauth.secret = "your_google_client_secret"
|
||||
///
|
||||
/// ## Use to configure github oauth
|
||||
/// # github_oauth.enabled = true
|
||||
/// # github_oauth.id = "your_github_client_id"
|
||||
/// # github_oauth.secret = "your_github_client_secret"
|
||||
///
|
||||
/// ## Use to configure google oauth
|
||||
/// # google_oauth.enabled = true
|
||||
/// # google_oauth.id = "your_google_client_id"
|
||||
/// # google_oauth.secret = "your_google_client_secret"
|
||||
/// ## an alternate base url that is used to recieve github webhook requests
|
||||
/// ## if empty or not specified, will use 'host' address as base
|
||||
/// ## default: empty (none)
|
||||
/// # github_webhook_base_url = "https://github-webhook.monitor.dev"
|
||||
///
|
||||
/// ## token that has to be given to github during repo webhook config as the secret
|
||||
/// ## default: empty (none)
|
||||
/// github_webhook_secret = "your_random_webhook_secret"
|
||||
///
|
||||
/// ## Configure github webhook app. Enables webhook management apis.
|
||||
/// # github_webhook_app.app_id = 1234455 # Find on the app page.
|
||||
/// # github_webhook_app.installations = [
|
||||
/// # ## Find the id after installing the app to user / organization. "namespace" is the username / organization name.
|
||||
/// # { id = 1234, namespace = "mbecker20" }
|
||||
/// # ]
|
||||
///
|
||||
/// ## Path to github webhook app private key.
|
||||
/// ## This is defaulted to `/github/private-key.pem`, and doesn't need to be changed if running in Docker.
|
||||
/// ## Just mount the private key pem file on the host to `/github/private-key.pem` in the container.
|
||||
/// # github_webhook_app.pk_path = "/path/to/pk.pem"
|
||||
///
|
||||
/// ## MUST comment back in some way to configure mongo.
|
||||
/// # mongo.uri = "mongodb://username:password@localhost:27017"
|
||||
@@ -243,16 +271,21 @@ fn default_config_path() -> String {
|
||||
/// [secrets]
|
||||
/// # SECRET_1 = "value_1"
|
||||
/// # SECRET_2 = "value_2"
|
||||
///
|
||||
/// ## provide core-based github accounts
|
||||
/// [github_accounts]
|
||||
/// # github_username_1 = "github_token_1"
|
||||
/// # github_username_2 = "github_token_2"
|
||||
///
|
||||
/// ## provide core-based docker accounts
|
||||
/// [docker_accounts]
|
||||
/// # docker_username_1 = "docker_token_1"
|
||||
/// # docker_username_2 = "docker_token_2"
|
||||
///
|
||||
/// ## configure git providers
|
||||
/// # [[git_provider]]
|
||||
/// # domain = "git.mogh.tech" # use a custom provider, like self-hosted gitea
|
||||
/// # accounts = [
|
||||
/// # { username = "mbecker20", token = "access_token_for_account" },
|
||||
/// # ]
|
||||
///
|
||||
/// ## configure docker registries
|
||||
/// # [[docker_registry]]
|
||||
/// # domain = "docker.io"
|
||||
/// # accounts = [
|
||||
/// # { username = "mbecker2020", token = "access_token_for_account" }
|
||||
/// # ]
|
||||
/// # organizations = ["DockerhubOrganization"]
|
||||
///
|
||||
/// ## configure aws ecr registries
|
||||
/// # [aws_ecr_registry.label_1]
|
||||
@@ -315,30 +348,6 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub keep_alerts_for_days: u64,
|
||||
|
||||
/// Used to verify validity from github webhooks.
|
||||
/// Should be some secure hash maybe 20-40 chars.
|
||||
/// It needs to be given to github when configuring the webhook.
|
||||
#[serde(default)]
|
||||
pub github_webhook_secret: String,
|
||||
|
||||
/// Used to form the frontend listener url, if None will use 'host'.
|
||||
///
|
||||
/// This can be used if core sits on an internal network which is
|
||||
/// unreachable directly from the open internet.
|
||||
/// A reverse proxy in a public network (with its own DNS)
|
||||
/// can forward webhooks to the internal monitor
|
||||
pub github_webhook_base_url: Option<String>,
|
||||
|
||||
/// Allowed github orgs used with monitor.
|
||||
/// Default: none.
|
||||
#[serde(default)]
|
||||
pub github_organizations: Vec<String>,
|
||||
|
||||
/// Allowed docker orgs used with monitor.
|
||||
/// Default: none.
|
||||
#[serde(default)]
|
||||
pub docker_organizations: Vec<String>,
|
||||
|
||||
/// Configure logging
|
||||
#[serde(default)]
|
||||
pub logging: LogConfig,
|
||||
@@ -355,13 +364,32 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub local_auth: bool,
|
||||
|
||||
/// Configure google oauth
|
||||
#[serde(default)]
|
||||
pub google_oauth: OauthCredentials,
|
||||
|
||||
/// Configure github oauth
|
||||
#[serde(default)]
|
||||
pub github_oauth: OauthCredentials,
|
||||
|
||||
/// Configure google oauth
|
||||
/// Used to verify validity from webhooks.
|
||||
/// Should be some secure hash maybe 20-40 chars.
|
||||
/// It is given to git provider when configuring the webhook.
|
||||
#[serde(default)]
|
||||
pub google_oauth: OauthCredentials,
|
||||
pub webhook_secret: String,
|
||||
|
||||
/// Override the webhook listener base url, if None will use the address defined as 'host'.
|
||||
/// Example: `https://webhooks.mogh.tech`
|
||||
///
|
||||
/// This can be used if core sits on an internal network which is
|
||||
/// unreachable directly from the open internet.
|
||||
/// A reverse proxy in a public network can forward webhooks to the internal monitor.
|
||||
pub webhook_base_url: Option<String>,
|
||||
|
||||
/// Configure a Github Webhook app.
|
||||
/// Allows users to manage repo webhooks from within the Monitor UI.
|
||||
#[serde(default)]
|
||||
pub github_webhook_app: GithubWebhookAppConfig,
|
||||
|
||||
/// Configure core mongo connection.
|
||||
///
|
||||
@@ -383,21 +411,19 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub secrets: HashMap<String, String>,
|
||||
|
||||
/// Configure core-based github accounts. These will be preferentially attached to build / repo clone
|
||||
/// requests if they contain a matching github account. Otherwise, the periphery will have to have the
|
||||
/// account configured.
|
||||
#[serde(default)]
|
||||
pub github_accounts: HashMap<String, String>,
|
||||
/// Configure git credentials used to clone private repos.
|
||||
/// Supports any git provider.
|
||||
#[serde(default, alias = "git_provider")]
|
||||
pub git_providers: Vec<GitProvider>,
|
||||
|
||||
/// Configure core-based docker accounts. These will be preferentially attached to build / deploy
|
||||
/// requests if they contain a matching docker account. Otherwise, the periphery will have to have the
|
||||
/// account configured.
|
||||
#[serde(default)]
|
||||
pub docker_accounts: HashMap<String, String>,
|
||||
/// Configure docker credentials used to push / pull images.
|
||||
/// Supports any docker image repository.
|
||||
#[serde(default, alias = "docker_registry")]
|
||||
pub docker_registries: Vec<DockerRegistry>,
|
||||
|
||||
/// Configure aws ecr registries.
|
||||
/// Configure aws ecr registries, which are handled differently than other registries
|
||||
#[serde(default, alias = "aws_ecr_registry")]
|
||||
pub aws_ecr_registries: HashMap<String, AwsEcrConfigWithCredentials>,
|
||||
pub aws_ecr_registries: Vec<AwsEcrConfigWithCredentials>,
|
||||
}
|
||||
|
||||
fn default_title() -> String {
|
||||
@@ -426,8 +452,7 @@ impl CoreConfig {
|
||||
let mut config = self.clone();
|
||||
|
||||
config.passkey = empty_or_redacted(&config.passkey);
|
||||
config.github_webhook_secret =
|
||||
empty_or_redacted(&config.github_webhook_secret);
|
||||
config.webhook_secret = empty_or_redacted(&config.webhook_secret);
|
||||
|
||||
config.github_oauth.id =
|
||||
empty_or_redacted(&config.github_oauth.id);
|
||||
@@ -539,6 +564,8 @@ pub struct HetznerCredentials {
|
||||
/// Provide configuration for an Aws Ecr registry.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct AwsEcrConfigWithCredentials {
|
||||
/// A label for the registry
|
||||
pub label: String,
|
||||
/// The Aws region
|
||||
pub region: String,
|
||||
/// The Aws account id
|
||||
@@ -565,4 +592,39 @@ impl AwsEcrConfig {
|
||||
account_id: config.account_id.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide configuration for a Github Webhook app.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GithubWebhookAppConfig {
|
||||
/// Github app id
|
||||
pub app_id: i64,
|
||||
/// Configure the app installations on multiple accounts / organizations.
|
||||
pub installations: Vec<GithubWebhookAppInstallationConfig>,
|
||||
/// Private key path. Default: /github-private-key.pem.
|
||||
#[serde(default = "default_private_key_path")]
|
||||
pub pk_path: String,
|
||||
}
|
||||
|
||||
fn default_private_key_path() -> String {
|
||||
String::from("/github/private-key.pem")
|
||||
}
|
||||
|
||||
impl Default for GithubWebhookAppConfig {
|
||||
fn default() -> Self {
|
||||
GithubWebhookAppConfig {
|
||||
app_id: 0,
|
||||
installations: Default::default(),
|
||||
pk_path: default_private_key_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide configuration for a Github Webhook app installation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GithubWebhookAppInstallationConfig {
|
||||
/// The installation ID
|
||||
pub id: i64,
|
||||
/// The user or organization name
|
||||
pub namespace: String,
|
||||
}
|
||||
|
||||
@@ -1,2 +1,87 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
pub mod core;
|
||||
pub mod periphery;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct GitProvider {
|
||||
/// The git provider domain. Default: `github.com`.
|
||||
#[serde(default = "default_git_provider")]
|
||||
pub domain: String,
|
||||
/// Whether to use https. Default: true.
|
||||
#[serde(default = "default_git_https")]
|
||||
pub https: bool,
|
||||
/// The account username. Required.
|
||||
#[serde(alias = "account")]
|
||||
pub accounts: Vec<ProviderAccount>,
|
||||
}
|
||||
|
||||
fn default_git_provider() -> String {
|
||||
String::from("github.com")
|
||||
}
|
||||
|
||||
fn default_git_https() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct DockerRegistry {
|
||||
/// The docker provider domain. Default: `docker.io`.
|
||||
#[serde(default = "default_docker_provider")]
|
||||
pub domain: String,
|
||||
/// The account username. Required.
|
||||
#[serde(default, alias = "account")]
|
||||
pub accounts: Vec<ProviderAccount>,
|
||||
/// Available organizations on the registry provider.
|
||||
/// Used to push an image under an organization's repo rather than an account's repo.
|
||||
#[serde(default, alias = "organization")]
|
||||
pub organizations: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_docker_provider() -> String {
|
||||
String::from("docker.io")
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct ProviderAccount {
|
||||
/// The account username. Required.
|
||||
#[serde(alias = "account")]
|
||||
pub username: String,
|
||||
/// The account access token. Required.
|
||||
#[serde(skip_serializing)]
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ use crate::entities::{
|
||||
Timelength,
|
||||
};
|
||||
|
||||
use super::{DockerRegistry, GitProvider};
|
||||
|
||||
/// # Periphery Command Line Arguments.
|
||||
///
|
||||
/// This structure represents the periphery command line arguments used to
|
||||
@@ -178,21 +180,27 @@ fn default_config_paths() -> Vec<String> {
|
||||
/// ## optional, default 'Monitor'.
|
||||
/// # logging.opentelemetry_service_name = "Monitor"
|
||||
///
|
||||
/// ## optional. can inject these values into your deployments configuration.
|
||||
/// ## configure perihery-based secrets
|
||||
/// [secrets]
|
||||
/// secret_variable = "secret_value"
|
||||
/// # SECRET_1 = "value_1"
|
||||
/// # SECRET_2 = "value_2"
|
||||
///
|
||||
/// ## optional. can use these accounts with deployments / builds.
|
||||
/// [github_accounts]
|
||||
/// github_username1 = "github_token1"
|
||||
/// github_username2 = "github_token2"
|
||||
///
|
||||
/// ## optional. can use these accounts with deployments / builds.
|
||||
/// [docker_accounts]
|
||||
/// docker_username1 = "docker_token1"
|
||||
/// docker_username2 = "docker_token2"
|
||||
/// ## configure periphery-based git providers
|
||||
/// # [[git_provider]]
|
||||
/// # domain = "git.mogh.tech" # use a custom provider, like self-hosted gitea
|
||||
/// # accounts = [
|
||||
/// # { username = "mbecker20", token = "access_token_for_account" },
|
||||
/// # ]
|
||||
///
|
||||
/// ## configure periphery-based docker registries
|
||||
/// # [[docker_registry]]
|
||||
/// # domain = "docker.io"
|
||||
/// # accounts = [
|
||||
/// # { username = "mbecker2020", token = "access_token_for_account" }
|
||||
/// # ]
|
||||
/// # organizations = ["DockerhubOrganization"]
|
||||
/// ```
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PeripheryConfig {
|
||||
/// The port periphery will run on.
|
||||
/// Default: `8120`
|
||||
@@ -232,15 +240,15 @@ pub struct PeripheryConfig {
|
||||
#[serde(default)]
|
||||
pub secrets: HashMap<String, String>,
|
||||
|
||||
/// Mapping of github usernames to access tokens.
|
||||
/// Default: none
|
||||
#[serde(default)]
|
||||
pub github_accounts: HashMap<String, String>,
|
||||
/// Configure git credentials used to clone private repos.
|
||||
/// Supports any git provider.
|
||||
#[serde(default, alias = "git_provider")]
|
||||
pub git_providers: Vec<GitProvider>,
|
||||
|
||||
/// Mapping of docker usernames to access tokens.
|
||||
/// Default: none
|
||||
#[serde(default)]
|
||||
pub docker_accounts: HashMap<String, String>,
|
||||
/// Configure docker credentials used to push / pull images.
|
||||
/// Supports any docker image repository.
|
||||
#[serde(default, alias = "docker_registry")]
|
||||
pub docker_registries: Vec<DockerRegistry>,
|
||||
}
|
||||
|
||||
fn default_periphery_port() -> u16 {
|
||||
|
||||
@@ -14,7 +14,6 @@ use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use super::{
|
||||
build::ImageRegistry,
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
@@ -61,16 +60,15 @@ pub struct DeploymentConfig {
|
||||
#[builder(default)]
|
||||
pub image: DeploymentImage,
|
||||
|
||||
/// Configure the registry used to pull the image from the registry.
|
||||
/// Configure the account used to pull the image from the registry.
|
||||
/// Used with `docker login`.
|
||||
///
|
||||
/// When using attached build as image source:
|
||||
/// - If the field is `None` variant, will use the same ImageRegistry config as the build.
|
||||
/// - Otherwise, it must match the variant of the ImageRegistry build config.
|
||||
/// - Only the account is used, the organization is not needed here
|
||||
/// - If the field is empty string, will use the same account config as the build, or none at all if using image.
|
||||
/// - If the field contains an account, a token for the account must be available.
|
||||
/// - Will get the registry domain from the build / image
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub image_registry: ImageRegistry,
|
||||
pub image_registry_account: String,
|
||||
|
||||
/// Whether to skip secret interpolation into the deployment environment variables.
|
||||
#[serde(default)]
|
||||
@@ -213,7 +211,7 @@ impl Default for DeploymentConfig {
|
||||
server_id: Default::default(),
|
||||
send_alerts: default_send_alerts(),
|
||||
image: Default::default(),
|
||||
image_registry: Default::default(),
|
||||
image_registry_account: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
redeploy_on_build: Default::default(),
|
||||
term_signal_labels: default_term_signal_labels(),
|
||||
@@ -309,21 +307,13 @@ pub fn conversions_from_str(
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let local = split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have 'local' key")
|
||||
})?
|
||||
.trim()
|
||||
.to_string();
|
||||
let (local, container) =
|
||||
line.split_once('=').with_context(|| {
|
||||
format!("line {i} missing assignment (=)")
|
||||
})?;
|
||||
let local = local.trim().to_string();
|
||||
// remove trailing comments
|
||||
let mut container_split = split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have 'container' key")
|
||||
})?
|
||||
.split('#');
|
||||
let mut container_split = container.split('#');
|
||||
let container = container_split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
@@ -618,20 +608,16 @@ pub fn term_signal_labels_from_str(
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let signal = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have signal"))?
|
||||
.trim()
|
||||
.parse::<TerminationSignal>()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have valid signal")
|
||||
let (signal, label) =
|
||||
line.split_once('=').with_context(|| {
|
||||
format!("line {i} missing assignment (=)")
|
||||
})?;
|
||||
let signal =
|
||||
signal.trim().parse::<TerminationSignal>().with_context(
|
||||
|| format!("line {i} does not have valid signal"),
|
||||
)?;
|
||||
// remove trailing comments
|
||||
let mut label_split = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have label"))?
|
||||
.split('#');
|
||||
let mut label_split = label.split('#');
|
||||
let label = label_split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have label"))?
|
||||
@@ -791,3 +777,16 @@ impl super::resource::AddFilters for DeploymentQuerySpecifics {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_registry_domain(
|
||||
image_name: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut split = image_name.split('/');
|
||||
let maybe_domain =
|
||||
split.next().context("image name cannot be empty string")?;
|
||||
if maybe_domain.contains('.') {
|
||||
Ok(maybe_domain.to_string())
|
||||
} else {
|
||||
Ok(String::from("docker.io"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::Context;
|
||||
use async_timing_util::unix_timestamp_ms;
|
||||
use build::CloudRegistryConfig;
|
||||
use build::StandardRegistryConfig;
|
||||
use clap::Parser;
|
||||
use config::core::AwsEcrConfig;
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
@@ -117,32 +117,7 @@ pub fn get_image_name(
|
||||
) -> anyhow::Result<String> {
|
||||
let name = to_monitor_name(name);
|
||||
let name = match image_registry {
|
||||
// TODO
|
||||
build::ImageRegistry::None(_) => name,
|
||||
build::ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
organization,
|
||||
account,
|
||||
}) => {
|
||||
if !organization.is_empty() {
|
||||
format!("{organization}/{name}")
|
||||
} else if !account.is_empty() {
|
||||
format!("{account}/{name}")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
build::ImageRegistry::Ghcr(CloudRegistryConfig {
|
||||
organization,
|
||||
account,
|
||||
}) => {
|
||||
if !organization.is_empty() {
|
||||
format!("ghcr.io/{organization}/{name}")
|
||||
} else if !account.is_empty() {
|
||||
format!("ghcr.io/{account}/{name}")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
build::ImageRegistry::AwsEcr(label) => {
|
||||
let AwsEcrConfig {
|
||||
region, account_id, ..
|
||||
@@ -151,16 +126,26 @@ pub fn get_image_name(
|
||||
})?;
|
||||
format!("{account_id}.dkr.ecr.{region}.amazonaws.com/{name}")
|
||||
}
|
||||
build::ImageRegistry::Custom(_) => {
|
||||
// TODO
|
||||
name
|
||||
build::ImageRegistry::Standard(StandardRegistryConfig {
|
||||
domain,
|
||||
account,
|
||||
organization,
|
||||
}) => {
|
||||
if !organization.is_empty() {
|
||||
let org = organization.to_lowercase();
|
||||
format!("{domain}/{org}/{name}")
|
||||
} else if !account.is_empty() {
|
||||
format!("{domain}/{account}/{name}")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
pub fn to_monitor_name(name: &str) -> String {
|
||||
name.to_lowercase().replace(' ', "_")
|
||||
name.to_lowercase().replace([' ', '.'], "_")
|
||||
}
|
||||
|
||||
pub fn monitor_timestamp() -> i64 {
|
||||
@@ -368,17 +353,13 @@ pub fn environment_vars_from_str(
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let variable = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have variable"))?
|
||||
.trim()
|
||||
.to_string();
|
||||
let (variable, value) =
|
||||
line.split_once('=').with_context(|| {
|
||||
format!("line {i} missing assignment (=)")
|
||||
})?;
|
||||
let variable = variable.trim().to_string();
|
||||
// remove trailing comments
|
||||
let mut value_split = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have value"))?
|
||||
.split('#');
|
||||
let mut value_split = value.split('#');
|
||||
let value = value_split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have value"))?
|
||||
@@ -498,6 +479,7 @@ impl<'de> Visitor<'de> for OptionEnvVarVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LatestCommit {
|
||||
pub hash: String,
|
||||
@@ -507,14 +489,26 @@ pub struct LatestCommit {
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CloneArgs {
|
||||
/// Resource name (eg Build name, Repo name)
|
||||
pub name: String,
|
||||
/// Git provider domain. Default: `github.com`
|
||||
pub provider: Option<String>,
|
||||
/// Use https (vs http).
|
||||
pub https: bool,
|
||||
/// Full repo identifier. <namespace>/<repo_name>
|
||||
pub repo: Option<String>,
|
||||
/// Git Branch. Default: `main`
|
||||
pub branch: Option<String>,
|
||||
/// Specific commit hash. Optional
|
||||
pub commit: Option<String>,
|
||||
/// The clone destination path
|
||||
pub destination: Option<String>,
|
||||
/// Command to run after the repo has been cloned
|
||||
pub on_clone: Option<SystemCommand>,
|
||||
/// Command to run after the repo has been pulled
|
||||
pub on_pull: Option<SystemCommand>,
|
||||
pub github_account: Option<String>,
|
||||
/// Configure the account used to access repo (if private)
|
||||
pub account: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&self::build::Build> for CloneArgs {
|
||||
@@ -527,7 +521,9 @@ impl From<&self::build::Build> for CloneArgs {
|
||||
destination: None,
|
||||
on_clone: build.config.pre_build.clone().into_option(),
|
||||
on_pull: None,
|
||||
github_account: optional_string(&build.config.github_account),
|
||||
provider: optional_string(&build.config.git_provider),
|
||||
https: build.config.git_https,
|
||||
account: optional_string(&build.config.git_account),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,7 +538,9 @@ impl From<&self::repo::Repo> for CloneArgs {
|
||||
destination: optional_string(&repo.config.path),
|
||||
on_clone: repo.config.on_clone.clone().into_option(),
|
||||
on_pull: repo.config.on_pull.clone().into_option(),
|
||||
github_account: optional_string(&repo.config.github_account),
|
||||
provider: optional_string(&repo.config.git_provider),
|
||||
https: repo.config.git_https,
|
||||
account: optional_string(&repo.config.git_account),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,10 +552,12 @@ impl From<&self::sync::ResourceSync> for CloneArgs {
|
||||
repo: optional_string(&sync.config.repo),
|
||||
branch: optional_string(&sync.config.branch),
|
||||
commit: optional_string(&sync.config.commit),
|
||||
github_account: optional_string(&sync.config.github_account),
|
||||
destination: None,
|
||||
on_clone: None,
|
||||
on_pull: None,
|
||||
provider: Some(String::from("github.com")),
|
||||
https: sync.config.git_https,
|
||||
account: optional_string(&sync.config.git_account),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ pub struct RepoListItemInfo {
|
||||
pub server_id: String,
|
||||
/// Repo last cloned / pulled timestamp in ms.
|
||||
pub last_pulled_at: I64,
|
||||
/// The configured github repo
|
||||
/// The git provider domain
|
||||
pub git_provider: String,
|
||||
/// The configured repo
|
||||
pub repo: String,
|
||||
/// The configured branch
|
||||
pub branch: String,
|
||||
@@ -77,6 +79,12 @@ pub struct RepoConfig {
|
||||
#[builder(default)]
|
||||
pub server_id: String,
|
||||
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
#[partial_default(default_git_provider())]
|
||||
pub git_provider: String,
|
||||
|
||||
/// The github repo to clone.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
@@ -93,11 +101,22 @@ pub struct RepoConfig {
|
||||
#[builder(default)]
|
||||
pub commit: String,
|
||||
|
||||
/// The github account to use to clone.
|
||||
/// It must be available in the server's periphery config.
|
||||
#[serde(default)]
|
||||
/// The git account used to access private repos.
|
||||
/// Passing empty string can only clone public repos.
|
||||
///
|
||||
/// Note. A token for the account must be available in the core config or the builder server's periphery config
|
||||
/// for the configured git provider.
|
||||
#[serde(default, alias = "github_account")]
|
||||
#[builder(default)]
|
||||
pub github_account: String,
|
||||
pub git_account: String,
|
||||
|
||||
/// Whether to use https to clone the repo (versus http). Default: true
|
||||
///
|
||||
/// Note. Monitor does not currently support cloning repos via ssh.
|
||||
#[serde(default = "default_git_https")]
|
||||
#[builder(default = "default_git_https()")]
|
||||
#[partial_default(default_git_https())]
|
||||
pub git_https: bool,
|
||||
|
||||
/// Explicitly specificy the folder to clone the repo in.
|
||||
#[serde(default)]
|
||||
@@ -129,6 +148,14 @@ impl RepoConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_git_provider() -> String {
|
||||
String::from("github.com")
|
||||
}
|
||||
|
||||
fn default_git_https() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_branch() -> String {
|
||||
String::from("main")
|
||||
}
|
||||
@@ -141,10 +168,12 @@ impl Default for RepoConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_id: Default::default(),
|
||||
git_provider: default_git_provider(),
|
||||
git_https: default_git_https(),
|
||||
repo: Default::default(),
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
git_account: Default::default(),
|
||||
path: Default::default(),
|
||||
on_clone: Default::default(),
|
||||
on_pull: Default::default(),
|
||||
|
||||
@@ -155,11 +155,12 @@ pub struct ServerHealth {
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Default,
|
||||
Display,
|
||||
EnumString,
|
||||
PartialEq,
|
||||
Eq,
|
||||
)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
|
||||
@@ -152,27 +152,47 @@ pub enum HetznerVolumeFormat {
|
||||
)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum HetznerServerType {
|
||||
SharedIntel1Core2Ram20Disk,
|
||||
// The lowest tier available at all locations
|
||||
// Shared
|
||||
#[default]
|
||||
/// CPX11 - AMD 2 Cores, 2 Gb Ram, 40 Gb disk
|
||||
SharedAmd2Core2Ram40Disk,
|
||||
/// CAX11 - Arm 2 Cores, 4 Gb Ram, 40 Gb disk
|
||||
SharedArm2Core4Ram40Disk,
|
||||
/// CX22 - Intel 2 Cores, 4 Gb Ram, 40 Gb disk
|
||||
SharedIntel2Core4Ram40Disk,
|
||||
/// CPX21 - AMD 3 Cores, 4 Gb Ram, 80 Gb disk
|
||||
SharedAmd3Core4Ram80Disk,
|
||||
/// CAX21 - Arm 4 Cores, 8 Gb Ram, 80 Gb disk
|
||||
SharedArm4Core8Ram80Disk,
|
||||
SharedIntel2Core8Ram80Disk,
|
||||
/// CX32 - Intel 4 Cores, 8 Gb Ram, 80 Gb disk
|
||||
SharedIntel4Core8Ram80Disk,
|
||||
/// CPX31 - AMD 4 Cores, 8 Gb Ram, 160 Gb disk
|
||||
SharedAmd4Core8Ram160Disk,
|
||||
/// CAX31 - Arm 8 Cores, 16 Gb Ram, 160 Gb disk
|
||||
SharedArm8Core16Ram160Disk,
|
||||
SharedIntel4Core16Ram160Disk,
|
||||
/// CX42 - Intel 8 Cores, 16 Gb Ram, 160 Gb disk
|
||||
SharedIntel8Core16Ram160Disk,
|
||||
/// CPX41 - AMD 8 Cores, 16 Gb Ram, 240 Gb disk
|
||||
SharedAmd8Core16Ram240Disk,
|
||||
/// CAX41 - Arm 16 Cores, 32 Gb Ram, 320 Gb disk
|
||||
SharedArm16Core32Ram320Disk,
|
||||
SharedIntel8Core32Ram240Disk,
|
||||
/// CX52 - Intel 16 Cores, 32 Gb Ram, 320 Gb disk
|
||||
SharedIntel16Core32Ram320Disk,
|
||||
/// CPX51 - AMD 16 Cores, 32 Gb Ram, 360 Gb disk
|
||||
SharedAmd16Core32Ram360Disk,
|
||||
|
||||
// Dedicated
|
||||
/// CCX13 - AMD 2 Cores, 8 Gb Ram, 80 Gb disk
|
||||
DedicatedAmd2Core8Ram80Disk,
|
||||
/// CCX23 - AMD 4 Cores, 16 Gb Ram, 160 Gb disk
|
||||
DedicatedAmd4Core16Ram160Disk,
|
||||
/// CCX33 - AMD 8 Cores, 32 Gb Ram, 240 Gb disk
|
||||
DedicatedAmd8Core32Ram240Disk,
|
||||
/// CCX43 - AMD 16 Cores, 64 Gb Ram, 360 Gb disk
|
||||
DedicatedAmd16Core64Ram360Disk,
|
||||
/// CCX53 - AMD 32 Cores, 128 Gb Ram, 600 Gb disk
|
||||
DedicatedAmd32Core128Ram600Disk,
|
||||
/// CCX63 - AMD 48 Cores, 192 Gb Ram, 960 Gb disk
|
||||
DedicatedAmd48Core192Ram960Disk,
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ pub struct ResourceSyncListItemInfo {
|
||||
pub last_sync_hash: String,
|
||||
/// Commit message of last sync, or empty string
|
||||
pub last_sync_message: String,
|
||||
/// The git provider domain
|
||||
pub git_provider: String,
|
||||
/// The Github repo used as the source of the sync resources
|
||||
pub repo: String,
|
||||
/// The branch of the repo
|
||||
@@ -165,6 +167,20 @@ pub type _PartialResourceSyncConfig = PartialResourceSyncConfig;
|
||||
#[partial_derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[partial(skip_serializing_none, from, diff)]
|
||||
pub struct ResourceSyncConfig {
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
#[partial_default(default_git_provider())]
|
||||
pub git_provider: String,
|
||||
|
||||
/// Whether to use https to clone the repo (versus http). Default: true
|
||||
///
|
||||
/// Note. Monitor does not currently support cloning repos via ssh.
|
||||
#[serde(default = "default_git_https")]
|
||||
#[builder(default = "default_git_https()")]
|
||||
#[partial_default(default_git_https())]
|
||||
pub git_https: bool,
|
||||
|
||||
/// The Github repo used as the source of the build.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
@@ -181,11 +197,14 @@ pub struct ResourceSyncConfig {
|
||||
#[builder(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)]
|
||||
/// The git account used to access private repos.
|
||||
/// Passing empty string can only clone public repos.
|
||||
///
|
||||
/// Note. A token for the account must be available in the core config or the builder server's periphery config
|
||||
/// for the configured git provider.
|
||||
#[serde(default, alias = "github_account")]
|
||||
#[builder(default)]
|
||||
pub github_account: String,
|
||||
pub git_account: String,
|
||||
|
||||
/// The github account used to clone (used to access private repos).
|
||||
/// Empty string is public clone (only public repos).
|
||||
@@ -213,6 +232,14 @@ impl ResourceSyncConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_git_provider() -> String {
|
||||
String::from("github.com")
|
||||
}
|
||||
|
||||
fn default_git_https() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_branch() -> String {
|
||||
String::from("main")
|
||||
}
|
||||
@@ -228,10 +255,12 @@ fn default_webhook_enabled() -> bool {
|
||||
impl Default for ResourceSyncConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
git_provider: default_git_provider(),
|
||||
git_https: default_git_https(),
|
||||
repo: Default::default(),
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
git_account: Default::default(),
|
||||
resource_path: default_resource_path(),
|
||||
delete: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
alerter::PartialAlerterConfig, build::PartialBuildConfig,
|
||||
builder::PartialBuilderConfig, deployment::PartialDeploymentConfig,
|
||||
permission::PermissionLevel, procedure::PartialProcedureConfig,
|
||||
repo::PartialRepoConfig, server::PartialServerConfig,
|
||||
alerter::PartialAlerterConfig,
|
||||
build::PartialBuildConfig,
|
||||
builder::PartialBuilderConfig,
|
||||
deployment::PartialDeploymentConfig,
|
||||
permission::PermissionLevel,
|
||||
procedure::PartialProcedureConfig,
|
||||
repo::PartialRepoConfig,
|
||||
server::PartialServerConfig,
|
||||
server_template::PartialServerTemplateConfig,
|
||||
sync::PartialResourceSyncConfig, update::ResourceTarget,
|
||||
sync::PartialResourceSyncConfig,
|
||||
update::{ResourceTarget, ResourceTargetVariant},
|
||||
variable::Variable,
|
||||
};
|
||||
|
||||
@@ -137,13 +144,26 @@ pub struct UserGroupToml {
|
||||
#[serde(default)]
|
||||
pub users: Vec<String>,
|
||||
|
||||
/// Give the user group elevated permissions on all resources of a certain type
|
||||
#[serde(default)]
|
||||
pub all: HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
|
||||
/// Permissions given to the group
|
||||
#[serde(default, rename = "permission")]
|
||||
#[serde(default, alias = "permission")]
|
||||
pub permissions: Vec<PermissionToml>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PermissionToml {
|
||||
/// Id can be:
|
||||
/// - resource name. `id = "abcd-build"`
|
||||
/// - regex matching resource names. `id = "\^(.+)-build-([0-9]+)$\"`
|
||||
pub target: ResourceTarget,
|
||||
|
||||
/// The permission level:
|
||||
/// - None
|
||||
/// - Read
|
||||
/// - Execute
|
||||
/// - Write
|
||||
pub level: PermissionLevel,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ use crate::entities::{
|
||||
use super::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate, Version,
|
||||
server::Server, server_template::ServerTemplate,
|
||||
sync::ResourceSync, Version,
|
||||
};
|
||||
|
||||
/// Represents an action performed by Monitor.
|
||||
@@ -188,18 +189,16 @@ impl Log {
|
||||
/// Used to reference a specific resource across all resource types
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
EnumVariants,
|
||||
)]
|
||||
#[variant_derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
@@ -207,6 +206,9 @@ impl Log {
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
AsRefStr
|
||||
@@ -303,6 +305,12 @@ impl From<&ServerTemplate> for ResourceTarget {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ResourceSync> for ResourceTarget {
|
||||
fn from(resource_sync: &ResourceSync) -> Self {
|
||||
Self::ResourceSync(resource_sync.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// An update's status
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use std::sync::OnceLock;
|
||||
use std::{collections::HashMap, sync::OnceLock};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{MongoId, I64};
|
||||
|
||||
use super::{
|
||||
permission::PermissionLevel, update::ResourceTargetVariant,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[cfg_attr(
|
||||
@@ -54,25 +58,13 @@ pub struct User {
|
||||
#[serde(default)]
|
||||
pub last_update_view: I64,
|
||||
|
||||
/// Recently viewed server ids
|
||||
/// Recently viewed ids
|
||||
#[serde(default)]
|
||||
pub recent_servers: Vec<String>,
|
||||
pub recents: HashMap<ResourceTargetVariant, Vec<String>>,
|
||||
|
||||
/// Recently viewed deployment ids
|
||||
/// Give the user elevated permissions on all resources of a certain type
|
||||
#[serde(default)]
|
||||
pub recent_deployments: Vec<String>,
|
||||
|
||||
/// Recently viewed build ids
|
||||
#[serde(default)]
|
||||
pub recent_builds: Vec<String>,
|
||||
|
||||
/// Recently viewed repo ids
|
||||
#[serde(default)]
|
||||
pub recent_repos: Vec<String>,
|
||||
|
||||
/// Recently viewed procedure ids
|
||||
#[serde(default)]
|
||||
pub recent_procedures: Vec<String>,
|
||||
pub all: HashMap<ResourceTargetVariant, PermissionLevel>,
|
||||
|
||||
#[serde(default)]
|
||||
pub updated_at: I64,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user