mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-17 06:01:04 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d1ff9760 | ||
|
|
dfafadf57b |
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -41,7 +41,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -943,7 +943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
@@ -1355,7 +1355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1439,7 +1439,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2192,7 +2192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2208,7 +2208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2239,7 +2239,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2296,7 +2296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2383,7 +2383,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2445,19 +2445,6 @@ dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.15.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
"envy",
|
||||
"logger",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -3102,7 +3089,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -4880,7 +4867,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -1,9 +1,15 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
members = [
|
||||
"bin/*",
|
||||
"lib/*",
|
||||
"example/*",
|
||||
"client/core/rs",
|
||||
"client/periphery/rs",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.15.8"
|
||||
version = "1.15.9"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -108,4 +114,4 @@ octorust = "0.7.0"
|
||||
dashmap = "6.1.0"
|
||||
colored = "2.1.0"
|
||||
regex = "1.11.0"
|
||||
bson = "2.13.0"
|
||||
bson = "2.13.0"
|
||||
|
||||
@@ -16,7 +16,7 @@ WORKDIR /builder
|
||||
COPY ./frontend ./frontend
|
||||
COPY ./client/core/ts ./client
|
||||
RUN cd client && yarn && yarn build && yarn link
|
||||
RUN cd frontend && yarn link @komodo/client && yarn && yarn build
|
||||
RUN cd frontend && yarn link komodo_client && yarn && yarn build
|
||||
|
||||
# Final Image
|
||||
FROM alpine:3.20
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /builder
|
||||
COPY ./frontend ./frontend
|
||||
COPY ./client/core/ts ./client
|
||||
RUN cd client && yarn && yarn build && yarn link
|
||||
RUN cd frontend && yarn link @komodo/client && yarn && yarn build
|
||||
RUN cd frontend && yarn link komodo_client && yarn && yarn build
|
||||
|
||||
# Final Image
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
@@ -6,7 +6,9 @@ use komodo_client::{
|
||||
api::execute::RunBuild,
|
||||
entities::{build::Build, user::git_webhook_user},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
@@ -20,22 +22,30 @@ fn build_locks() -> &'static ListenerLockCache {
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_build_webhook(
|
||||
build_id: String,
|
||||
pub async fn auth_build_webhook(
|
||||
build_id: &str,
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> serror::Result<Build> {
|
||||
let build = resource::get::<Build>(build_id)
|
||||
.await
|
||||
.status_code(StatusCode::NOT_FOUND)?;
|
||||
verify_gh_signature(headers, body, &build.config.webhook_secret)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
pub async fn handle_build_webhook(
|
||||
build: Build,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = build_locks().get_or_insert_default(&build_id).await;
|
||||
let lock = build_locks().get_or_insert_default(&build.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let build = resource::get::<Build>(&build_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &build.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !build.config.webhook_enabled {
|
||||
return Err(anyhow!("build does not have webhook enabled"));
|
||||
}
|
||||
@@ -46,7 +56,7 @@ pub async fn handle_build_webhook(
|
||||
}
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req = ExecuteRequest::RunBuild(RunBuild { build: build_id });
|
||||
let req = ExecuteRequest::RunBuild(RunBuild { build: build.id });
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunBuild(req) = req else {
|
||||
unreachable!()
|
||||
|
||||
@@ -39,10 +39,11 @@ pub fn router() -> Router {
|
||||
"/build/:id",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let build = build::auth_build_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("build_webhook", id);
|
||||
async {
|
||||
let res = build::handle_build_webhook(id.clone(), headers, body).await;
|
||||
let res = build::handle_build_webhook(build, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run build webook for build {id} | {e:#}");
|
||||
}
|
||||
@@ -50,6 +51,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -57,10 +59,11 @@ pub fn router() -> Router {
|
||||
"/repo/:id/clone",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let repo = repo::auth_repo_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_clone_webhook", id);
|
||||
async {
|
||||
let res = repo::handle_repo_clone_webhook(id.clone(), headers, body).await;
|
||||
let res = repo::handle_repo_clone_webhook(repo, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo clone webook for repo {id} | {e:#}");
|
||||
}
|
||||
@@ -68,6 +71,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -75,10 +79,11 @@ pub fn router() -> Router {
|
||||
"/repo/:id/pull",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let repo = repo::auth_repo_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_pull_webhook", id);
|
||||
async {
|
||||
let res = repo::handle_repo_pull_webhook(id.clone(), headers, body).await;
|
||||
let res = repo::handle_repo_pull_webhook(repo, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo pull webook for repo {id} | {e:#}");
|
||||
}
|
||||
@@ -86,6 +91,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -93,10 +99,11 @@ pub fn router() -> Router {
|
||||
"/repo/:id/build",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let repo = repo::auth_repo_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("repo_build_webhook", id);
|
||||
async {
|
||||
let res = repo::handle_repo_build_webhook(id.clone(), headers, body).await;
|
||||
let res = repo::handle_repo_build_webhook(repo, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run repo build webook for repo {id} | {e:#}");
|
||||
}
|
||||
@@ -104,6 +111,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -111,10 +119,11 @@ pub fn router() -> Router {
|
||||
"/stack/:id/refresh",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let stack = stack::auth_stack_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("stack_clone_webhook", id);
|
||||
async {
|
||||
let res = stack::handle_stack_refresh_webhook(id.clone(), headers, body).await;
|
||||
let res = stack::handle_stack_refresh_webhook(stack, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run stack clone webook for stack {id} | {e:#}");
|
||||
}
|
||||
@@ -122,6 +131,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -129,10 +139,11 @@ pub fn router() -> Router {
|
||||
"/stack/:id/deploy",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let stack = stack::auth_stack_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("stack_pull_webhook", id);
|
||||
async {
|
||||
let res = stack::handle_stack_deploy_webhook(id.clone(), headers, body).await;
|
||||
let res = stack::handle_stack_deploy_webhook(stack, body).await;
|
||||
if let Err(e) = res {
|
||||
warn!("failed to run stack pull webook for stack {id} | {e:#}");
|
||||
}
|
||||
@@ -140,6 +151,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -147,13 +159,13 @@ pub fn router() -> Router {
|
||||
"/procedure/:id/:branch",
|
||||
post(
|
||||
|Path(IdBranch { id, branch }), headers: HeaderMap, body: String| async move {
|
||||
let procedure = procedure::auth_procedure_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("procedure_webhook", id, branch);
|
||||
async {
|
||||
let res = procedure::handle_procedure_webhook(
|
||||
id.clone(),
|
||||
procedure,
|
||||
branch.unwrap_or_else(|| String::from("main")),
|
||||
headers,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
@@ -163,6 +175,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -170,12 +183,12 @@ pub fn router() -> Router {
|
||||
"/sync/:id/refresh",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let sync = sync::auth_sync_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_refresh_webhook", id);
|
||||
async {
|
||||
let res = sync::handle_sync_refresh_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
sync,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
@@ -185,6 +198,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -192,12 +206,12 @@ pub fn router() -> Router {
|
||||
"/sync/:id/sync",
|
||||
post(
|
||||
|Path(Id { id }), headers: HeaderMap, body: String| async move {
|
||||
let sync = sync::auth_sync_webhook(&id, headers, &body).await?;
|
||||
tokio::spawn(async move {
|
||||
let span = info_span!("sync_execute_webhook", id);
|
||||
async {
|
||||
let res = sync::handle_sync_execute_webhook(
|
||||
id.clone(),
|
||||
headers,
|
||||
sync,
|
||||
body
|
||||
).await;
|
||||
if let Err(e) = res {
|
||||
@@ -207,6 +221,7 @@ pub fn router() -> Router {
|
||||
.instrument(span)
|
||||
.await
|
||||
});
|
||||
serror::Result::Ok(())
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -6,7 +6,9 @@ use komodo_client::{
|
||||
api::execute::RunProcedure,
|
||||
entities::{procedure::Procedure, user::git_webhook_user},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
@@ -20,28 +22,36 @@ fn procedure_locks() -> &'static ListenerLockCache {
|
||||
BUILD_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_procedure_webhook(
|
||||
procedure_id: String,
|
||||
target_branch: String,
|
||||
pub async fn auth_procedure_webhook(
|
||||
procedure_id: &str,
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> serror::Result<Procedure> {
|
||||
let procedure = resource::get::<Procedure>(procedure_id)
|
||||
.await
|
||||
.status_code(StatusCode::NOT_FOUND)?;
|
||||
verify_gh_signature(
|
||||
headers,
|
||||
body,
|
||||
&procedure.config.webhook_secret,
|
||||
)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
Ok(procedure)
|
||||
}
|
||||
|
||||
pub async fn handle_procedure_webhook(
|
||||
procedure: Procedure,
|
||||
target_branch: String,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock =
|
||||
procedure_locks().get_or_insert_default(&procedure_id).await;
|
||||
procedure_locks().get_or_insert_default(&procedure.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let procedure = resource::get::<Procedure>(&procedure_id).await?;
|
||||
|
||||
verify_gh_signature(
|
||||
headers,
|
||||
&body,
|
||||
&procedure.config.webhook_secret,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !procedure.config.webhook_enabled {
|
||||
return Err(anyhow!("procedure does not have webhook enabled"));
|
||||
}
|
||||
@@ -53,7 +63,7 @@ pub async fn handle_procedure_webhook(
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req = ExecuteRequest::RunProcedure(RunProcedure {
|
||||
procedure: procedure_id,
|
||||
procedure: procedure.id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunProcedure(req) = req else {
|
||||
|
||||
@@ -6,7 +6,9 @@ use komodo_client::{
|
||||
api::execute::{BuildRepo, CloneRepo, PullRepo},
|
||||
entities::{repo::Repo, user::git_webhook_user},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
helpers::update::init_execution_update, resource, state::State,
|
||||
@@ -19,22 +21,30 @@ fn repo_locks() -> &'static ListenerLockCache {
|
||||
REPO_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_repo_clone_webhook(
|
||||
repo_id: String,
|
||||
pub async fn auth_repo_webhook(
|
||||
repo_id: &str,
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> serror::Result<Repo> {
|
||||
let repo = resource::get::<Repo>(repo_id)
|
||||
.await
|
||||
.status_code(StatusCode::NOT_FOUND)?;
|
||||
verify_gh_signature(headers, body, &repo.config.webhook_secret)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
pub async fn handle_repo_clone_webhook(
|
||||
repo: Repo,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = repo_locks().get_or_insert_default(&repo_id).await;
|
||||
let lock = repo_locks().get_or_insert_default(&repo.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let repo = resource::get::<Repo>(&repo_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &repo.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
@@ -47,7 +57,7 @@ pub async fn handle_repo_clone_webhook(
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req =
|
||||
crate::api::execute::ExecuteRequest::CloneRepo(CloneRepo {
|
||||
repo: repo_id,
|
||||
repo: repo.id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::CloneRepo(req) = req
|
||||
@@ -59,21 +69,15 @@ pub async fn handle_repo_clone_webhook(
|
||||
}
|
||||
|
||||
pub async fn handle_repo_pull_webhook(
|
||||
repo_id: String,
|
||||
headers: HeaderMap,
|
||||
repo: Repo,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = repo_locks().get_or_insert_default(&repo_id).await;
|
||||
let lock = repo_locks().get_or_insert_default(&repo.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let repo = resource::get::<Repo>(&repo_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &repo.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
@@ -85,7 +89,7 @@ pub async fn handle_repo_pull_webhook(
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req = crate::api::execute::ExecuteRequest::PullRepo(PullRepo {
|
||||
repo: repo_id,
|
||||
repo: repo.id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::PullRepo(req) = req else {
|
||||
@@ -96,21 +100,15 @@ pub async fn handle_repo_pull_webhook(
|
||||
}
|
||||
|
||||
pub async fn handle_repo_build_webhook(
|
||||
repo_id: String,
|
||||
headers: HeaderMap,
|
||||
repo: Repo,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = repo_locks().get_or_insert_default(&repo_id).await;
|
||||
let lock = repo_locks().get_or_insert_default(&repo.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let repo = resource::get::<Repo>(&repo_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &repo.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !repo.config.webhook_enabled {
|
||||
return Err(anyhow!("repo does not have webhook enabled"));
|
||||
}
|
||||
@@ -123,7 +121,7 @@ pub async fn handle_repo_build_webhook(
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req =
|
||||
crate::api::execute::ExecuteRequest::BuildRepo(BuildRepo {
|
||||
repo: repo_id,
|
||||
repo: repo.id,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let crate::api::execute::ExecuteRequest::BuildRepo(req) = req
|
||||
|
||||
@@ -9,7 +9,9 @@ use komodo_client::{
|
||||
},
|
||||
entities::{stack::Stack, user::git_webhook_user},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
@@ -23,22 +25,30 @@ fn stack_locks() -> &'static ListenerLockCache {
|
||||
STACK_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_stack_refresh_webhook(
|
||||
stack_id: String,
|
||||
pub async fn auth_stack_webhook(
|
||||
stack_id: &str,
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> serror::Result<Stack> {
|
||||
let stack = resource::get::<Stack>(stack_id)
|
||||
.await
|
||||
.status_code(StatusCode::NOT_FOUND)?;
|
||||
verify_gh_signature(headers, body, &stack.config.webhook_secret)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
Ok(stack)
|
||||
}
|
||||
|
||||
pub async fn handle_stack_refresh_webhook(
|
||||
stack: Stack,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through, from "action state busy".
|
||||
let lock = stack_locks().get_or_insert_default(&stack_id).await;
|
||||
let lock = stack_locks().get_or_insert_default(&stack.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let stack = resource::get::<Stack>(&stack_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &stack.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !stack.config.webhook_enabled {
|
||||
return Err(anyhow!("stack does not have webhook enabled"));
|
||||
}
|
||||
@@ -56,21 +66,15 @@ pub async fn handle_stack_refresh_webhook(
|
||||
}
|
||||
|
||||
pub async fn handle_stack_deploy_webhook(
|
||||
stack_id: String,
|
||||
headers: HeaderMap,
|
||||
stack: Stack,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = stack_locks().get_or_insert_default(&stack_id).await;
|
||||
let lock = stack_locks().get_or_insert_default(&stack.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let stack = resource::get::<Stack>(&stack_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &stack.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !stack.config.webhook_enabled {
|
||||
return Err(anyhow!("stack does not have webhook enabled"));
|
||||
}
|
||||
@@ -83,7 +87,7 @@ pub async fn handle_stack_deploy_webhook(
|
||||
let user = git_webhook_user().to_owned();
|
||||
if stack.config.webhook_force_deploy {
|
||||
let req = ExecuteRequest::DeployStack(DeployStack {
|
||||
stack: stack_id,
|
||||
stack: stack.id,
|
||||
stop_time: None,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
@@ -94,7 +98,7 @@ pub async fn handle_stack_deploy_webhook(
|
||||
} else {
|
||||
let req =
|
||||
ExecuteRequest::DeployStackIfChanged(DeployStackIfChanged {
|
||||
stack: stack_id,
|
||||
stack: stack.id,
|
||||
stop_time: None,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
|
||||
@@ -6,7 +6,9 @@ use komodo_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
entities::{sync::ResourceSync, user::git_webhook_user},
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use serror::AddStatusCode;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
@@ -20,22 +22,30 @@ fn sync_locks() -> &'static ListenerLockCache {
|
||||
SYNC_LOCKS.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn handle_sync_refresh_webhook(
|
||||
sync_id: String,
|
||||
pub async fn auth_sync_webhook(
|
||||
sync_id: &str,
|
||||
headers: HeaderMap,
|
||||
body: &str,
|
||||
) -> serror::Result<ResourceSync> {
|
||||
let sync = resource::get::<ResourceSync>(sync_id)
|
||||
.await
|
||||
.status_code(StatusCode::NOT_FOUND)?;
|
||||
verify_gh_signature(headers, body, &sync.config.webhook_secret)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
Ok(sync)
|
||||
}
|
||||
|
||||
pub async fn handle_sync_refresh_webhook(
|
||||
sync: ResourceSync,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let lock = sync_locks().get_or_insert_default(&sync.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &sync.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
@@ -47,27 +57,21 @@ pub async fn handle_sync_refresh_webhook(
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
State
|
||||
.resolve(RefreshResourceSyncPending { sync: sync_id }, user)
|
||||
.resolve(RefreshResourceSyncPending { sync: sync.id }, user)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_sync_execute_webhook(
|
||||
sync_id: String,
|
||||
headers: HeaderMap,
|
||||
sync: ResourceSync,
|
||||
body: String,
|
||||
) -> anyhow::Result<()> {
|
||||
// Acquire and hold lock to make a task queue for
|
||||
// subsequent listener calls on same resource.
|
||||
// It would fail if we let it go through from action state busy.
|
||||
let lock = sync_locks().get_or_insert_default(&sync_id).await;
|
||||
let lock = sync_locks().get_or_insert_default(&sync.id).await;
|
||||
let _lock = lock.lock().await;
|
||||
|
||||
let sync = resource::get::<ResourceSync>(&sync_id).await?;
|
||||
|
||||
verify_gh_signature(headers, &body, &sync.config.webhook_secret)
|
||||
.await?;
|
||||
|
||||
if !sync.config.webhook_enabled {
|
||||
return Err(anyhow!("sync does not have webhook enabled"));
|
||||
}
|
||||
@@ -79,7 +83,7 @@ pub async fn handle_sync_execute_webhook(
|
||||
|
||||
let user = git_webhook_user().to_owned();
|
||||
let req = ExecuteRequest::RunSync(RunSync {
|
||||
sync: sync_id,
|
||||
sync: sync.id,
|
||||
resource_type: None,
|
||||
resources: None,
|
||||
});
|
||||
|
||||
@@ -2,9 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
resource::ResourceQuery,
|
||||
server::{Server, ServerListItem},
|
||||
user::User,
|
||||
resource::ResourceQuery, server::Server, user::User,
|
||||
};
|
||||
|
||||
use crate::resource;
|
||||
@@ -32,11 +30,10 @@ pub async fn check_alerts(ts: i64) {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
async fn get_all_servers_map() -> anyhow::Result<(
|
||||
HashMap<String, ServerListItem>,
|
||||
HashMap<String, String>,
|
||||
)> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
async fn get_all_servers_map(
|
||||
) -> anyhow::Result<(HashMap<String, Server>, HashMap<String, String>)>
|
||||
{
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
ResourceQuery::default(),
|
||||
&User {
|
||||
admin: true,
|
||||
|
||||
@@ -5,7 +5,7 @@ use derive_variants::ExtractVariant;
|
||||
use komodo_client::entities::{
|
||||
alert::{Alert, AlertData, AlertDataVariant, SeverityLevel},
|
||||
komodo_timestamp, optional_string,
|
||||
server::{ServerListItem, ServerState},
|
||||
server::{Server, ServerState},
|
||||
ResourceTarget,
|
||||
};
|
||||
use mongo_indexed::Indexed;
|
||||
@@ -28,7 +28,7 @@ type OpenDiskAlertMap = OpenAlertMap<PathBuf>;
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn alert_servers(
|
||||
ts: i64,
|
||||
mut servers: HashMap<String, ServerListItem>,
|
||||
mut servers: HashMap<String, Server>,
|
||||
) {
|
||||
let server_statuses = server_status_cache().get_list().await;
|
||||
|
||||
@@ -70,12 +70,12 @@ pub async fn alert_servers(
|
||||
data: AlertData::ServerUnreachable {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
err: server_status.err.clone(),
|
||||
},
|
||||
};
|
||||
alerts_to_open
|
||||
.push((alert, server.info.send_unreachable_alerts))
|
||||
.push((alert, server.config.send_unreachable_alerts))
|
||||
}
|
||||
(ServerState::NotOk, Some(alert)) => {
|
||||
// update alert err
|
||||
@@ -102,8 +102,10 @@ pub async fn alert_servers(
|
||||
|
||||
// Close an open alert
|
||||
(ServerState::Ok | ServerState::Disabled, Some(alert)) => {
|
||||
alert_ids_to_close
|
||||
.push((alert.clone(), server.info.send_unreachable_alerts));
|
||||
alert_ids_to_close.push((
|
||||
alert.clone(),
|
||||
server.config.send_unreachable_alerts,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -119,20 +121,21 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerCpu))
|
||||
.cloned();
|
||||
match (health.cpu, cpu_alert) {
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None) => {
|
||||
match (health.cpu.level, cpu_alert, health.cpu.should_close_alert)
|
||||
{
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
|
||||
// open alert
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: false,
|
||||
resolved_ts: None,
|
||||
level: health.cpu,
|
||||
level: health.cpu.level,
|
||||
target: ResourceTarget::Server(server_status.id.clone()),
|
||||
data: AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
percentage: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
@@ -140,41 +143,44 @@ pub async fn alert_servers(
|
||||
.unwrap_or(0.0),
|
||||
},
|
||||
};
|
||||
alerts_to_open.push((alert, server.info.send_cpu_alerts));
|
||||
alerts_to_open.push((alert, server.config.send_cpu_alerts));
|
||||
}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
) => {
|
||||
// modify alert level only if it has increased
|
||||
if alert.level < health.cpu {
|
||||
alert.level = health.cpu;
|
||||
if alert.level < health.cpu.level {
|
||||
alert.level = health.cpu.level;
|
||||
alert.data = AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
percentage: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
.map(|s| s.cpu_perc as f64)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alerts_to_update.push((alert, server.info.send_cpu_alerts));
|
||||
alerts_to_update
|
||||
.push((alert, server.config.send_cpu_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerCpu {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
percentage: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
.map(|s| s.cpu_perc as f64)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close.push((alert, server.info.send_cpu_alerts))
|
||||
alert_ids_to_close
|
||||
.push((alert, server.config.send_cpu_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -186,20 +192,21 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(&AlertDataVariant::ServerMem))
|
||||
.cloned();
|
||||
match (health.mem, mem_alert) {
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None) => {
|
||||
match (health.mem.level, mem_alert, health.mem.should_close_alert)
|
||||
{
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None, _) => {
|
||||
// open alert
|
||||
let alert = Alert {
|
||||
id: Default::default(),
|
||||
ts,
|
||||
resolved: false,
|
||||
resolved_ts: None,
|
||||
level: health.mem,
|
||||
level: health.mem.level,
|
||||
target: ResourceTarget::Server(server_status.id.clone()),
|
||||
data: AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
total_gb: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
@@ -212,19 +219,20 @@ pub async fn alert_servers(
|
||||
.unwrap_or(0.0),
|
||||
},
|
||||
};
|
||||
alerts_to_open.push((alert, server.info.send_mem_alerts));
|
||||
alerts_to_open.push((alert, server.config.send_mem_alerts));
|
||||
}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
) => {
|
||||
// modify alert level only if it has increased
|
||||
if alert.level < health.mem {
|
||||
alert.level = health.mem;
|
||||
if alert.level < health.mem.level {
|
||||
alert.level = health.mem.level;
|
||||
alert.data = AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
total_gb: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
@@ -236,15 +244,16 @@ pub async fn alert_servers(
|
||||
.map(|s| s.mem_used_gb)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alerts_to_update.push((alert, server.info.send_mem_alerts));
|
||||
alerts_to_update
|
||||
.push((alert, server.config.send_mem_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
let mut alert = alert.clone();
|
||||
alert.data = AlertData::ServerMem {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
total_gb: server_status
|
||||
.stats
|
||||
.as_ref()
|
||||
@@ -256,7 +265,8 @@ pub async fn alert_servers(
|
||||
.map(|s| s.mem_used_gb)
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
alert_ids_to_close.push((alert, server.info.send_mem_alerts))
|
||||
alert_ids_to_close
|
||||
.push((alert, server.config.send_mem_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -273,8 +283,12 @@ pub async fn alert_servers(
|
||||
.as_ref()
|
||||
.and_then(|alerts| alerts.get(path))
|
||||
.cloned();
|
||||
match (*health, disk_alert) {
|
||||
(SeverityLevel::Warning | SeverityLevel::Critical, None) => {
|
||||
match (health.level, disk_alert, health.should_close_alert) {
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
None,
|
||||
_,
|
||||
) => {
|
||||
let disk = server_status.stats.as_ref().and_then(|stats| {
|
||||
stats.disks.iter().find(|disk| disk.mount == *path)
|
||||
});
|
||||
@@ -283,58 +297,60 @@ pub async fn alert_servers(
|
||||
ts,
|
||||
resolved: false,
|
||||
resolved_ts: None,
|
||||
level: *health,
|
||||
level: health.level,
|
||||
target: ResourceTarget::Server(server_status.id.clone()),
|
||||
data: AlertData::ServerDisk {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
path: path.to_owned(),
|
||||
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
|
||||
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
|
||||
},
|
||||
};
|
||||
alerts_to_open.push((alert, server.info.send_disk_alerts));
|
||||
alerts_to_open
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
(
|
||||
SeverityLevel::Warning | SeverityLevel::Critical,
|
||||
Some(mut alert),
|
||||
_,
|
||||
) => {
|
||||
// Disk is persistent, update alert if health changes regardless of direction
|
||||
if *health != alert.level {
|
||||
if health.level != alert.level {
|
||||
let disk =
|
||||
server_status.stats.as_ref().and_then(|stats| {
|
||||
stats.disks.iter().find(|disk| disk.mount == *path)
|
||||
});
|
||||
alert.level = *health;
|
||||
alert.level = health.level;
|
||||
alert.data = AlertData::ServerDisk {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
path: path.to_owned(),
|
||||
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
|
||||
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
|
||||
};
|
||||
alerts_to_update
|
||||
.push((alert, server.info.send_disk_alerts));
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
}
|
||||
(SeverityLevel::Ok, Some(alert)) => {
|
||||
(SeverityLevel::Ok, Some(alert), true) => {
|
||||
let mut alert = alert.clone();
|
||||
let disk = server_status.stats.as_ref().and_then(|stats| {
|
||||
stats.disks.iter().find(|disk| disk.mount == *path)
|
||||
});
|
||||
alert.level = *health;
|
||||
alert.level = health.level;
|
||||
alert.data = AlertData::ServerDisk {
|
||||
id: server_status.id.clone(),
|
||||
name: server.name.clone(),
|
||||
region: optional_string(&server.info.region),
|
||||
region: optional_string(&server.config.region),
|
||||
path: path.to_owned(),
|
||||
total_gb: disk.map(|d| d.total_gb).unwrap_or_default(),
|
||||
used_gb: disk.map(|d| d.used_gb).unwrap_or_default(),
|
||||
};
|
||||
alert_ids_to_close
|
||||
.push((alert, server.info.send_disk_alerts))
|
||||
.push((alert, server.config.send_disk_alerts))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -347,7 +363,7 @@ pub async fn alert_servers(
|
||||
let mut alert = alert.clone();
|
||||
alert.level = SeverityLevel::Ok;
|
||||
alert_ids_to_close
|
||||
.push((alert, server.info.send_disk_alerts));
|
||||
.push((alert, server.config.send_disk_alerts));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ use komodo_client::entities::{
|
||||
network::NetworkListItem, volume::VolumeListItem,
|
||||
},
|
||||
repo::Repo,
|
||||
server::{Server, ServerConfig, ServerHealth, ServerState},
|
||||
server::{
|
||||
Server, ServerConfig, ServerHealth, ServerHealthState,
|
||||
ServerState,
|
||||
},
|
||||
stack::{ComposeProject, Stack, StackState},
|
||||
stats::{SingleDiskUsage, SystemStats},
|
||||
};
|
||||
@@ -126,6 +129,8 @@ pub async fn insert_server_status(
|
||||
.await;
|
||||
}
|
||||
|
||||
const ALERT_PERCENTAGE_THRESHOLD: f32 = 5.0;
|
||||
|
||||
fn get_server_health(
|
||||
server: &Server,
|
||||
SystemStats {
|
||||
@@ -148,16 +153,22 @@ fn get_server_health(
|
||||
let mut health = ServerHealth::default();
|
||||
|
||||
if cpu_perc >= cpu_critical {
|
||||
health.cpu = SeverityLevel::Critical
|
||||
health.cpu.level = SeverityLevel::Critical;
|
||||
} else if cpu_perc >= cpu_warning {
|
||||
health.cpu = SeverityLevel::Warning
|
||||
health.cpu.level = SeverityLevel::Warning
|
||||
} else if *cpu_perc < cpu_warning - ALERT_PERCENTAGE_THRESHOLD {
|
||||
health.cpu.should_close_alert = true
|
||||
}
|
||||
|
||||
let mem_perc = 100.0 * mem_used_gb / mem_total_gb;
|
||||
if mem_perc >= *mem_critical {
|
||||
health.mem = SeverityLevel::Critical
|
||||
health.mem.level = SeverityLevel::Critical
|
||||
} else if mem_perc >= *mem_warning {
|
||||
health.mem = SeverityLevel::Warning
|
||||
health.mem.level = SeverityLevel::Warning
|
||||
} else if mem_perc
|
||||
< mem_warning - (ALERT_PERCENTAGE_THRESHOLD as f64)
|
||||
{
|
||||
health.mem.should_close_alert = true
|
||||
}
|
||||
|
||||
for SingleDiskUsage {
|
||||
@@ -168,14 +179,17 @@ fn get_server_health(
|
||||
} in disks
|
||||
{
|
||||
let perc = 100.0 * used_gb / total_gb;
|
||||
let stats_state = if perc >= *disk_critical {
|
||||
SeverityLevel::Critical
|
||||
let mut state = ServerHealthState::default();
|
||||
if perc >= *disk_critical {
|
||||
state.level = SeverityLevel::Critical;
|
||||
} else if perc >= *disk_warning {
|
||||
SeverityLevel::Warning
|
||||
} else {
|
||||
SeverityLevel::Ok
|
||||
state.level = SeverityLevel::Warning;
|
||||
} else if perc
|
||||
< disk_warning - (ALERT_PERCENTAGE_THRESHOLD as f64)
|
||||
{
|
||||
state.should_close_alert = true;
|
||||
};
|
||||
health.disks.insert(mount.clone(), stats_state);
|
||||
health.disks.insert(mount.clone(), state);
|
||||
}
|
||||
|
||||
health
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "migrator"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# komodo_client.workspace = true
|
||||
logger.workspace = true
|
||||
#
|
||||
# mungos.workspace = true
|
||||
#
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
dotenvy.workspace = true
|
||||
envy.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -1,16 +0,0 @@
|
||||
FROM rust:1.80.1-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/komodo
|
||||
LABEL org.opencontainers.image.description="Database migrator for Komodo version upgrades"
|
||||
LABEL org.opencontainers.image.licenses=GPL-3.0
|
||||
|
||||
CMD ["./migrator"]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Migrator
|
||||
|
||||
Performs schema changes on the Komodo database
|
||||
|
||||
## v1.7 - v1.11 migration
|
||||
Run this before upgrading to latest from versions 1.7 to 1.11.
|
||||
```sh
|
||||
docker run --rm --name komodo-migrator \
|
||||
--network "host" \
|
||||
--env MIGRATION="v1.11" \
|
||||
--env TARGET_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env TARGET_DB_NAME="<DB_NAME>" \
|
||||
ghcr.io/mbecker20/komodo_migrator
|
||||
```
|
||||
|
||||
## v1.0 - v1.6 migration
|
||||
Run this before upgrading to latest from versions 1.0 to 1.6.
|
||||
```sh
|
||||
docker run --rm --name komodo-migrator \
|
||||
--network "host" \
|
||||
--env MIGRATION="v1.6" \
|
||||
--env TARGET_URI="mongodb://<USERNAME>:<PASSWORD>@<ADDRESS>" \
|
||||
--env TARGET_DB_NAME="<DB_NAME>" \
|
||||
ghcr.io/mbecker20/komodo_migrator
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
#[allow(unused)]
|
||||
pub mod v1_11;
|
||||
@@ -1,261 +0,0 @@
|
||||
use komodo_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 komodo_client::entities::build::Build {
|
||||
fn from(value: Build) -> Self {
|
||||
komodo_client::entities::build::Build {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
updated_at: value.updated_at,
|
||||
tags: value.tags,
|
||||
info: komodo_client::entities::build::BuildInfo {
|
||||
last_built_at: value.info.last_built_at,
|
||||
built_hash: None,
|
||||
built_message: None,
|
||||
latest_hash: None,
|
||||
latest_message: None,
|
||||
},
|
||||
config: value.config.into(),
|
||||
base_permission: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 = "komodo_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 = "komodo_client::entities::env_vars_deserializer"
|
||||
)]
|
||||
pub secret_args: Vec<EnvironmentVar>,
|
||||
|
||||
/// Docker labels
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "komodo_client::entities::env_vars_deserializer"
|
||||
)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
}
|
||||
|
||||
impl From<BuildConfig>
|
||||
for komodo_client::entities::build::BuildConfig
|
||||
{
|
||||
fn from(value: BuildConfig) -> Self {
|
||||
komodo_client::entities::build::BuildConfig {
|
||||
builder_id: value.builder_id,
|
||||
skip_secret_interp: value.skip_secret_interp,
|
||||
version: komodo_client::entities::Version {
|
||||
major: value.version.major,
|
||||
minor: value.version.minor,
|
||||
patch: value.version.patch,
|
||||
},
|
||||
links: Default::default(),
|
||||
auto_increment_version: true,
|
||||
image_name: Default::default(),
|
||||
image_tag: Default::default(),
|
||||
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: komodo_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,
|
||||
webhook_secret: Default::default(),
|
||||
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 komodo_client::entities::build::ImageRegistry
|
||||
{
|
||||
fn from(value: ImageRegistry) -> Self {
|
||||
match value {
|
||||
ImageRegistry::None(_) | ImageRegistry::Custom(_) => {
|
||||
komodo_client::entities::build::ImageRegistry::None(NoData {})
|
||||
}
|
||||
ImageRegistry::DockerHub(params) => {
|
||||
komodo_client::entities::build::ImageRegistry::Standard(
|
||||
StandardRegistryConfig {
|
||||
domain: String::from("docker.io"),
|
||||
account: params.account,
|
||||
organization: params.organization,
|
||||
},
|
||||
)
|
||||
}
|
||||
ImageRegistry::Ghcr(params) => {
|
||||
komodo_client::entities::build::ImageRegistry::Standard(
|
||||
StandardRegistryConfig {
|
||||
domain: String::from("ghcr.io"),
|
||||
account: params.account,
|
||||
organization: params.organization,
|
||||
},
|
||||
)
|
||||
}
|
||||
ImageRegistry::AwsEcr(label) => {
|
||||
komodo_client::entities::build::ImageRegistry::None(NoData {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use komodo_client::entities::{
|
||||
deployment::{
|
||||
conversions_deserializer, term_labels_deserializer, Conversion,
|
||||
DeploymentImage, RestartMode, TerminationSignalLabel,
|
||||
},
|
||||
env_vars_deserializer, EnvironmentVar, TerminationSignal,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{build::ImageRegistry, resource::Resource};
|
||||
|
||||
pub type Deployment = Resource<DeploymentConfig, ()>;
|
||||
|
||||
impl From<Deployment>
|
||||
for komodo_client::entities::deployment::Deployment
|
||||
{
|
||||
fn from(value: Deployment) -> Self {
|
||||
komodo_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(),
|
||||
base_permission: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 Komodo 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 komodo_client::entities::deployment::DeploymentConfig
|
||||
{
|
||||
fn from(value: DeploymentConfig) -> Self {
|
||||
komodo_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,
|
||||
links: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// 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,
|
||||
// }
|
||||
@@ -1,54 +0,0 @@
|
||||
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,46 +0,0 @@
|
||||
#![allow(unused)]
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
mod legacy;
|
||||
mod migrate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum Migration {
|
||||
#[serde(alias = "v1.11")]
|
||||
V1_11,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
migration: Migration,
|
||||
target_uri: String,
|
||||
target_db_name: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenvy::dotenv().ok();
|
||||
logger::init(&Default::default())?;
|
||||
|
||||
info!("starting migrator");
|
||||
|
||||
let env: Env = envy::from_env()?;
|
||||
|
||||
match env.migration {
|
||||
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!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod v1_11;
|
||||
@@ -1,70 +0,0 @@
|
||||
// use anyhow::Context;
|
||||
// use komodo_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(())
|
||||
// }
|
||||
@@ -91,10 +91,13 @@ impl StatsClient {
|
||||
}
|
||||
|
||||
pub fn get_system_stats(&self) -> SystemStats {
|
||||
let total_mem = self.system.total_memory();
|
||||
let available_mem = self.system.available_memory();
|
||||
SystemStats {
|
||||
cpu_perc: self.system.global_cpu_usage(),
|
||||
mem_used_gb: self.system.used_memory() as f64 / BYTES_PER_GB,
|
||||
mem_total_gb: self.system.total_memory() as f64 / BYTES_PER_GB,
|
||||
mem_free_gb: self.system.free_memory() as f64 / BYTES_PER_GB,
|
||||
mem_used_gb: (total_mem - available_mem) as f64 / BYTES_PER_GB,
|
||||
mem_total_gb: total_mem as f64 / BYTES_PER_GB,
|
||||
disks: self.get_disks(),
|
||||
polling_rate: self.stats.polling_rate,
|
||||
refresh_ts: self.stats.refresh_ts,
|
||||
|
||||
109
changelog.md
109
changelog.md
@@ -1,109 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## <ins>Komodo v1.14 (Sep 2024)</ins>
|
||||
- Renamed the project to **Komodo**.
|
||||
- Manage docker networks, volumes, and images.
|
||||
- Manage Containers at the server level, without creating any Deployment.
|
||||
- Add bulk Start / Restart / Pause actions for all containers on a server.
|
||||
- Add **Secret** mode to Variables to hide the value in updates / logs
|
||||
- Secret mode also prevents any non-admin users from retrieving the value from the API. Non admin users will still see the variable name.
|
||||
- Interpolate Variables / Secrets into everything I could think of
|
||||
- Deployment / Stack / Repo / Build **extra args**.
|
||||
- Deployment **command**.
|
||||
- Build **pre build**.
|
||||
- Repo **on_clone / on_pull**.
|
||||
- Added **Hetzner Singapore** datacenter for Hetzner ServerTemplates
|
||||
- **Removed Google Font** - now just use system local font to avoid any third party calls.
|
||||
|
||||
## <ins>Monitor v1.13 - Komodo (Aug 2024)</ins>
|
||||
- This is the first named release, as I think it is really big. The Komodo Dragon is the largest species of Monitor lizard.
|
||||
- **Deploy docker compose** with the new **Stack** resource.
|
||||
- Can define the compose file in the UI, or direct Monitor to clone a git repo containing compose files.
|
||||
- Use webhooks to redeploy the stack on push to the repo
|
||||
- Manage the environment variables passed to the compose command.
|
||||
- **Builds** can now be configured with an alternate repository name to push the image under.
|
||||
-An optional tag can also be configured to be postfixed onto the version, like image:1.13-aarch64.
|
||||
This helps for pushing alternate build configurations under the same image repo, just under different tags.
|
||||
- **Repos** can now be "built" using builders. The idea is, you spawn an AWS instance, clone a repo, execute a shell command
|
||||
(like running a script in the repo), and terminating the instance. The script can build a binary, and push it to some binary repository.
|
||||
Users will have to manage their own versioning though.
|
||||
- **High level UI Updates** courtesy of @karamvirsingh98
|
||||
|
||||
## <ins>v1.12 (July 2024)</ins>
|
||||
- Break free of Github dependance. Use other git providers, including self hosted ones.
|
||||
- Same for Docker registry. You can also now use any docker registry for your images.
|
||||
|
||||
## <ins>v1 (Spring 2024)</ins>
|
||||
|
||||
- **New resource types**:
|
||||
- **Repo**: Clone / pull configured repositories on desired Server. Run shell commands in the repo on every clone / pull to acheive automation. Listen for pushes to a particular branch to automatically pull the repo and run the command.
|
||||
- **Procedure**: Combine multiple *executions* (Deploy, RunBuild) and run them in sequence or in parallel. *RunProcedure* is an execution type, meaning procedures can run *other procedures*.
|
||||
- **Builder**: Ephemeral builder configuration has moved to being an API / UI managed entity for greater observability and ease of management.
|
||||
- **Alerter**: Define multiple alerting endpoints and manage them via the API / UI.
|
||||
- Slack support continues with the *Slack* Alerter variant.
|
||||
- Send JSON serialized alert data to any HTTP endpoint with the *Custom* Alerter variant.
|
||||
- **Template**: Define a template for your cloud provider's VM configuration
|
||||
- Launch VMs based on the template and automatically add them as Monitor servers.
|
||||
- Supports AWS EC2 and Hetzner Cloud.
|
||||
- **Sync**: Sync resources declared in toml files in Github repos.
|
||||
- Manage resources declaratively, with git history for configuration rollbacks.
|
||||
- See the actions which will be performed in the UI, and execute them upon manual confirmation.
|
||||
- Use a Git webhook to automatically execute syncs on git push.
|
||||
|
||||
- **Resource Tagging**
|
||||
- Attach multiple *tags* to resources, which can be used to group related resources together. These can be used to filter resources in the UI.
|
||||
- For example, resources can be given tags for *environment*, like `Prod`, `Uat`, or `Dev`. This can be combined with tags for the larger system the resource is a part of, such as `Authentication`, `Logging`, or `Messaging`.
|
||||
- Proper tagging will make it easy to find resources and visualize system components, even as the number of resources grows large.
|
||||
|
||||
- **Variables**
|
||||
- Manage global, non-secret key-value pairs using the API / UI.
|
||||
- These values can be interpolated into deployment `environments` and build `build_args`
|
||||
|
||||
- **Core Accounts and Secrets**
|
||||
- Docker / Github accounts and Secrets can now be configured in the Core configuration file.
|
||||
- They can still be added to the Periphery configuration as before. Accounts / Secrets defined in the Core configuration will be preferentially used over like ones defined in Periphery configuration.
|
||||
|
||||
- **User Groups**
|
||||
- Admins can now create User Groups and assign permissions to them as if they were a user.
|
||||
- Multiple users can then be added to the group, and a user can be added to multiple groups.
|
||||
- Users in the group inherit the group's permissions.
|
||||
|
||||
- **Builds**
|
||||
- Build log displays the **latest commit hash and message**.
|
||||
- In-progress builds are able to be **cancelled before completion**.
|
||||
- Specify a specific commit hash to build from.
|
||||
|
||||
- **Deployments**
|
||||
- Filter log lines using multiple search terms.
|
||||
|
||||
- **Alerting**
|
||||
- The alerting system has been redesigned with a stateful model.
|
||||
- Alerts can be in an Open or a Resolved state, and alerts are only sent on state changes.
|
||||
- For example, say a server has just crossed 80% memory usage, the configured memory threshold. An alert will be created in the Open state and the alert data will be sent out. Later, it has dropped down to 70%. The alert will be changed to the Resolved state and the alert data will again be sent.
|
||||
- In addition to server usage alerts, Monitor now supports deployment state change alerts. These are sent when a deployment's state changes without being caused by a Monitor action. For example, if a deployment goes from the Running state to the Exited state unexpectedly, say from a crash, an alert will be sent.
|
||||
- Current and past alerts can be retrieved using the API and viewed on the UI.
|
||||
|
||||
- **New UI**:
|
||||
- The Monitor UI has been revamped to support the new features and improve the user experience.
|
||||
|
||||
## <ins>v0 (Winter 2022)</ins>
|
||||
|
||||
- Move Core and Periphery implementation to Rust.
|
||||
- Add AWS Ec2 ephemeral build instance support.
|
||||
- Configuration added to core configuration file.
|
||||
- Automatic build versioning system, supporting image rollback.
|
||||
- Realtime and historical system stats - CPU, memory, disk.
|
||||
- Simple stats alerting based on out-of-bounds values for system stats.
|
||||
- Support sending alerts to Slack.
|
||||
|
||||
## <ins>Pre-versioned releases</ins>
|
||||
|
||||
- Defined main resource types:
|
||||
- Server
|
||||
- Deployment
|
||||
- Build
|
||||
- Basics of Monitor:
|
||||
- Build docker images from Github repos.
|
||||
- Manage image deployment on connected servers, see container status, get container logs.
|
||||
- Add account credentials in Periphery configuration.
|
||||
- Core and Periphery implemented in Typescript.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Komodo
|
||||
*A system to build and deploy software accross many servers*
|
||||
*A system to build and deploy software across many servers*
|
||||
|
||||
Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client)
|
||||
@@ -7,11 +7,14 @@
|
||||
//! - Path: `/auth`, `/user`, `/read`, `/write`, `/execute`
|
||||
//! - Headers:
|
||||
//! - Content-Type: `application/json`
|
||||
//! - Authorication: `your_jwt`
|
||||
//! - Authorization: `your_jwt`
|
||||
//! - X-Api-Key: `your_api_key`
|
||||
//! - X-Api-Secret: `your_api_secret`
|
||||
//! - Use either Authorization *or* X-Api-Key and X-Api-Secret to authenticate requests.
|
||||
//! - Body: JSON specifying the request type (`type`) and the parameters (`params`).
|
||||
//!
|
||||
//! You can create API keys for your user, or for a Service User with limited permissions,
|
||||
//! from the Komodo UI Settings page.
|
||||
//!
|
||||
//! To call the api, construct JSON bodies following
|
||||
//! the schemas given in [read], [mod@write], [execute], and so on.
|
||||
@@ -28,6 +31,18 @@
|
||||
//!
|
||||
//! The request's parent module (eg. [read], [mod@write]) determines the http path which
|
||||
//! must be used for the requests. For example, requests under [read] are made using http path `/read`.
|
||||
//!
|
||||
//! ## Curl Example
|
||||
//!
|
||||
//! Putting it all together, here is an example `curl` for [write::UpdateBuild], to update the version:
|
||||
//!
|
||||
//! ```text
|
||||
//! curl --header "Content-Type: application/json" \
|
||||
//! --header "X-Api-Key: your_api_key" \
|
||||
//! --header "X-Api-Secret: your_api_secret" \
|
||||
//! --data '{ "type": "UpdateBuild", "params": { "id": "67076689ed600cfdd52ac637", "config": { "version": "1.15.9" } } }' \
|
||||
//! https://komodo.example.com/write
|
||||
//! ```
|
||||
//!
|
||||
//! ## Modules
|
||||
//!
|
||||
|
||||
@@ -223,13 +223,22 @@ impl Default for ServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// The health of a part of the server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
||||
pub struct ServerHealthState {
|
||||
pub level: SeverityLevel,
|
||||
/// Whether the health is good enough to close an open alert.
|
||||
pub should_close_alert: bool,
|
||||
}
|
||||
|
||||
/// Summary of the health of the server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
||||
pub struct ServerHealth {
|
||||
pub cpu: SeverityLevel,
|
||||
pub mem: SeverityLevel,
|
||||
pub disks: HashMap<PathBuf, SeverityLevel>,
|
||||
pub cpu: ServerHealthState,
|
||||
pub mem: ServerHealthState,
|
||||
pub disks: HashMap<PathBuf, ServerHealthState>,
|
||||
}
|
||||
|
||||
/// Current pending actions on the server.
|
||||
|
||||
@@ -59,7 +59,13 @@ pub struct SystemStatsRecord {
|
||||
pub struct SystemStats {
|
||||
/// Cpu usage percentage
|
||||
pub cpu_perc: f32,
|
||||
/// Memory used in GB
|
||||
/// [1.15.9+]
|
||||
/// Free memory in GB.
|
||||
/// This is really the 'Free' memory, not the 'Available' memory.
|
||||
/// It may be different than mem_total_gb - mem_used_gb.
|
||||
#[serde(default)]
|
||||
pub mem_free_gb: f64,
|
||||
/// Used memory in GB. 'Total' - 'Available' (not free) memory.
|
||||
pub mem_used_gb: f64,
|
||||
/// Total memory in GB
|
||||
pub mem_total_gb: f64,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! # Komodo
|
||||
//! *A system to build and deploy software accross many servers*
|
||||
//! *A system to build and deploy software across many servers*
|
||||
//!
|
||||
//! This is a client library for the Komodo Core API.
|
||||
//! It contains:
|
||||
@@ -35,9 +35,9 @@ use serde::Deserialize;
|
||||
pub mod api;
|
||||
pub mod busy;
|
||||
pub mod entities;
|
||||
pub mod parser;
|
||||
pub mod ws;
|
||||
|
||||
mod parser;
|
||||
mod request;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -55,3 +55,51 @@ pub fn parse_key_value_list(
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
}
|
||||
|
||||
/// Parses commands out of multiline string
|
||||
/// and chains them together with '&&'
|
||||
///
|
||||
/// Supports full line and end of line comments, and escaped newlines.
|
||||
///
|
||||
/// ## Example:
|
||||
/// ```sh
|
||||
/// # comments supported
|
||||
/// sh ./shell1.sh # end of line supported
|
||||
/// sh ./shell2.sh
|
||||
///
|
||||
/// # escaped newlines supported
|
||||
/// curl --header "Content-Type: application/json" \
|
||||
/// --request POST \
|
||||
/// --data '{"key": "value"}' \
|
||||
/// https://destination.com
|
||||
///
|
||||
/// # print done
|
||||
/// echo done
|
||||
/// ```
|
||||
/// becomes
|
||||
/// ```sh
|
||||
/// sh ./shell1.sh && sh ./shell2.sh && {long curl command} && echo done
|
||||
/// ```
|
||||
pub fn parse_multiline_command(command: impl AsRef<str>) -> String {
|
||||
command
|
||||
.as_ref()
|
||||
// Remove comments and join back
|
||||
.split('\n')
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
.filter_map(|line| line.split(" #").next())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
// Remove escaped newlines
|
||||
.split(" \\")
|
||||
.map(str::trim)
|
||||
.fold(String::new(), |acc, el| acc + " " + el)
|
||||
// Then final split by newlines and join with &&
|
||||
.split('\n')
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
.filter_map(|line| line.split(" #").next())
|
||||
.map(str::trim)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" && ")
|
||||
}
|
||||
|
||||
37
client/core/ts/README.md
Normal file
37
client/core/ts/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Komodo
|
||||
|
||||
_A system to build and deploy software across many servers_
|
||||
|
||||
```sh
|
||||
npm install komodo_client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add komodo_client
|
||||
```
|
||||
|
||||
```ts
|
||||
import { KomodoClient, Types } from "komodo_client";
|
||||
|
||||
const komodo = KomodoClient("https://demo.komo.do", {
|
||||
type: "api-key",
|
||||
params: {
|
||||
api_key: "your_key",
|
||||
secret: "your secret",
|
||||
},
|
||||
});
|
||||
|
||||
const stacks: Types.StackListItem[] = await komodo.read({
|
||||
type: "ListStacks",
|
||||
params: {},
|
||||
});
|
||||
|
||||
const stack: Types.Stack = await komodo.read({
|
||||
type: "GetStack",
|
||||
params: {
|
||||
stack: stacks[0].name,
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -1,16 +1,21 @@
|
||||
{
|
||||
"name": "@komodo/client",
|
||||
"version": "1.0.0",
|
||||
"name": "komodo_client",
|
||||
"version": "1.15.9",
|
||||
"description": "Komodo client package",
|
||||
"homepage": "https://komo.do",
|
||||
"main": "dist/lib.js",
|
||||
"license": "MIT",
|
||||
"license": "GPL-3.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0"
|
||||
"axios": "^1.7.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.1.6"
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -5,25 +5,25 @@ import {
|
||||
ReadResponses,
|
||||
UserResponses,
|
||||
WriteResponses,
|
||||
} from "./responses";
|
||||
} from "./responses.js";
|
||||
import {
|
||||
AuthRequest,
|
||||
ExecuteRequest,
|
||||
ReadRequest,
|
||||
UserRequest,
|
||||
WriteRequest,
|
||||
} from "./types";
|
||||
} from "./types.js";
|
||||
|
||||
export * as Types from "./types";
|
||||
export * as Types from "./types.js";
|
||||
|
||||
type InitOptions =
|
||||
| { type: "jwt"; params: { jwt: string } }
|
||||
| { type: "api-key"; params: { api_key: string; secret: string } };
|
||||
| { type: "api-key"; params: { key: string; secret: string } };
|
||||
|
||||
export function KomodoClient(url: string, options: InitOptions) {
|
||||
const state = {
|
||||
jwt: options.type === "jwt" ? options.params.jwt : undefined,
|
||||
api_key: options.type === "api-key" ? options.params.api_key : undefined,
|
||||
key: options.type === "api-key" ? options.params.key : undefined,
|
||||
secret: options.type === "api-key" ? options.params.secret : undefined,
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
.post<Res>(url + path, request, {
|
||||
headers: {
|
||||
Authorization: state.jwt,
|
||||
"X-API-KEY": state.api_key,
|
||||
"X-API-KEY": state.key,
|
||||
"X-API-SECRET": state.secret,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as Types from "./types";
|
||||
import * as Types from "./types.js";
|
||||
|
||||
export type AuthResponses = {
|
||||
GetLoginOptions: Types.GetLoginOptionsResponse;
|
||||
|
||||
@@ -2243,7 +2243,14 @@ export enum Timelength {
|
||||
export interface SystemStats {
|
||||
/** Cpu usage percentage */
|
||||
cpu_perc: number;
|
||||
/** Memory used in GB */
|
||||
/**
|
||||
* [1.15.9+]
|
||||
* Free memory in GB.
|
||||
* This is really the 'Free' memory, not the 'Available' memory.
|
||||
* It may be different than mem_total_gb - mem_used_gb.
|
||||
*/
|
||||
mem_free_gb?: number;
|
||||
/** Used memory in GB. 'Total' - 'Available' (not free) memory. */
|
||||
mem_used_gb: number;
|
||||
/** Total memory in GB */
|
||||
mem_total_gb: number;
|
||||
@@ -6435,11 +6442,18 @@ export interface CloneArgs {
|
||||
account?: string;
|
||||
}
|
||||
|
||||
/** The health of a part of the server. */
|
||||
export interface ServerHealthState {
|
||||
level: SeverityLevel;
|
||||
/** Whether the health is good enough to close an open alert. */
|
||||
should_close_alert: boolean;
|
||||
}
|
||||
|
||||
/** Summary of the health of the server. */
|
||||
export interface ServerHealth {
|
||||
cpu: SeverityLevel;
|
||||
mem: SeverityLevel;
|
||||
disks: Record<string, SeverityLevel>;
|
||||
cpu: ServerHealthState;
|
||||
mem: ServerHealthState;
|
||||
disks: Record<string, ServerHealthState>;
|
||||
}
|
||||
|
||||
export enum AwsVolumeType {
|
||||
|
||||
@@ -7,12 +7,12 @@ asynckit@^0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
axios@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
|
||||
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
|
||||
axios@^1.7.7:
|
||||
version "1.7.7"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
|
||||
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
@@ -28,10 +28,10 @@ delayed-stream@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
follow-redirects@^1.15.6:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -59,7 +59,7 @@ proxy-from-env@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
typescript@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
|
||||
typescript@^5.6.3:
|
||||
version "5.6.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b"
|
||||
integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
|
||||
|
||||
@@ -43,10 +43,4 @@ This works by:
|
||||
Just like all other resources with Environments (Deployments, Repos, Builds),
|
||||
Stack Environments support **Variable and Secret interpolation**. Define global variables
|
||||
in the UI and share the values across environments.
|
||||
:::
|
||||
|
||||
## Using bind mounts
|
||||
|
||||
Repo-based stacks must delete the stack folder before it is able to reclone for the latest repo contents.
|
||||
Because of this, users should **avoid using relative file paths that are placed inside the repo directory**.
|
||||
Or better yet, make things simple and use **absolute file paths** or **docker volumes** instead.
|
||||
:::
|
||||
@@ -12,7 +12,7 @@ const FeatureList: FeatureItem[] = [
|
||||
title: "Automated builds 🛠️",
|
||||
description: (
|
||||
<>
|
||||
Build auto versioned docker images from github repos, trigger builds on
|
||||
Build auto versioned docker images from git repos, trigger builds on
|
||||
git push
|
||||
</>
|
||||
),
|
||||
|
||||
@@ -8,6 +8,23 @@ use axum::{routing::post, Json, Router};
|
||||
use komodo_client::entities::alert::{Alert, SeverityLevel};
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Entrypoint for handling each incoming alert.
|
||||
async fn handle_incoming_alert(Json(alert): Json<Alert>) {
|
||||
if alert.resolved {
|
||||
info!("Alert Resolved!: {alert:?}");
|
||||
return;
|
||||
}
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => info!("{alert:?}"),
|
||||
SeverityLevel::Warning => warn!("{alert:?}"),
|
||||
SeverityLevel::Critical => error!("{alert:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// ========================
|
||||
/// Http server boilerplate.
|
||||
/// ========================
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
#[serde(default = "default_port")]
|
||||
@@ -30,20 +47,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
info!("v {} | {socket_addr}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
post(|Json(alert): Json<Alert>| async move {
|
||||
if alert.resolved {
|
||||
info!("Alert Resolved!: {alert:?}");
|
||||
return;
|
||||
}
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => info!("{alert:?}"),
|
||||
SeverityLevel::Warning => warn!("{alert:?}"),
|
||||
SeverityLevel::Critical => error!("{alert:?}"),
|
||||
}
|
||||
}),
|
||||
);
|
||||
let app = Router::new().route("/", post(handle_incoming_alert));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(socket_addr)
|
||||
.await
|
||||
@@ -1,7 +1,16 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
use komodo_client::KomodoClient;
|
||||
use komodo_client::{ws::UpdateWsMessage, KomodoClient};
|
||||
|
||||
/// Entrypoint for handling each incoming update.
|
||||
async fn handle_incoming_update(update: UpdateWsMessage) {
|
||||
info!("{update:?}");
|
||||
}
|
||||
|
||||
/// ========================
|
||||
/// Ws Listener boilerplate.
|
||||
/// ========================
|
||||
|
||||
async fn app() -> anyhow::Result<()> {
|
||||
logger::init(&Default::default())?;
|
||||
@@ -13,12 +22,14 @@ async fn app() -> anyhow::Result<()> {
|
||||
let (mut rx, _) = komodo.subscribe_to_updates(1000, 5)?;
|
||||
|
||||
loop {
|
||||
let msg = rx.recv().await;
|
||||
if let Err(e) = msg {
|
||||
error!("🚨 recv error | {e:?}");
|
||||
break;
|
||||
}
|
||||
info!("{msg:?}");
|
||||
let update = match rx.recv().await {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
error!("🚨 recv error | {e:?}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
handle_incoming_update(update).await
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -9,7 +9,7 @@ ARG VITE_KOMODO_HOST
|
||||
ENV VITE_KOMODO_HOST ${VITE_KOMODO_HOST}
|
||||
|
||||
RUN cd client && yarn && yarn build && yarn link
|
||||
RUN cd frontend && yarn link @komodo/client && yarn && yarn build
|
||||
RUN cd frontend && yarn link komodo_client && yarn && yarn build
|
||||
|
||||
ENV PORT 4174
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ Komodo JS stack uses Yarn + Vite + React + Tailwind + shadcn/ui
|
||||
|
||||
## Setup Dev Environment
|
||||
|
||||
The frontend depends on the local package `@komodo/client` located at `/client/core/ts`.
|
||||
The frontend depends on the local package `komodo_client` located at `/client/core/ts`.
|
||||
This must first be built and prepared for yarn link.
|
||||
|
||||
The following command should setup everything up (run with /frontend as working directory):
|
||||
|
||||
```sh
|
||||
cd ../client/core/ts && yarn && yarn build && yarn link && \
|
||||
cd ../../../frontend && yarn link @komodo/client && yarn
|
||||
cd ../../../frontend && yarn link komodo_client && yarn
|
||||
```
|
||||
|
||||
You can make a new file `.env.development` (gitignored) which holds:
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { MonacoEditor } from "@components/monaco";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const AlertDetailsDialog = ({ id }: { id: string }) => {
|
||||
const [open, set] = useState(false);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Section } from "@components/layouts";
|
||||
import { alert_level_intention } from "@lib/color";
|
||||
import { useRead, atomWithStorage } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { useAtom } from "jotai";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { AlertLevel } from ".";
|
||||
import { AlertDetailsDialog } from "./details";
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AlertLevel } from ".";
|
||||
import { ResourceLink } from "@components/resources/common";
|
||||
import { UsableResource } from "@types";
|
||||
import { Dialog } from "@ui/dialog";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
import { AlertDetailsDialogContent } from "./details";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SecretSelector } from "@components/config/util";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
|
||||
export const SecretsSearch = ({
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "@components/config/util";
|
||||
import { Section } from "@components/layouts";
|
||||
import { MonacoLanguage } from "@components/monaco";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useCtrlKeyListener, useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
@@ -594,7 +594,7 @@ export function ConfirmUpdate<T>({
|
||||
file_contents_language,
|
||||
}: ConfirmUpdateProps<T>) {
|
||||
const [open, set] = useState(false);
|
||||
useCtrlKeyListener("s", () => {
|
||||
useCtrlKeyListener("Enter", () => {
|
||||
if (open) {
|
||||
onConfirm();
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CopyButton } from "@components/util";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { Input } from "@ui/input";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@ui/dialog";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { ResourceComponents } from "./resources";
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@ui/card";
|
||||
import { ResourceTags } from "./tags";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { logToHtml } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@ui/select";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
@@ -31,17 +31,34 @@ export const MonacoEditor = ({
|
||||
const [editor, setEditor] =
|
||||
useState<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
|
||||
let node = editor.getDomNode();
|
||||
if (!node) return;
|
||||
|
||||
const callback = (e: any) => {
|
||||
if (e.key === "Escape") {
|
||||
(document.activeElement as any)?.blur?.();
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener("keydown", callback);
|
||||
return () => node.removeEventListener("keydown", callback);
|
||||
}, [editor]);
|
||||
|
||||
const line_count = value?.split(/\r\n|\r|\n/).length ?? 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
const contentHeight = line_count * 18 + 30;
|
||||
const node = editor.getContainerDomNode();
|
||||
node.style.height = `${Math.max(
|
||||
const containerNode = editor.getContainerDomNode();
|
||||
|
||||
containerNode.style.height = `${Math.max(
|
||||
Math.ceil(contentHeight),
|
||||
MIN_EDITOR_HEIGHT
|
||||
)}px`;
|
||||
// node.style.height = `${Math.max(
|
||||
// containerNode.style.height = `${Math.max(
|
||||
// Math.min(Math.ceil(contentHeight), MAX_EDITOR_HEIGHT),
|
||||
// MIN_EDITOR_HEIGHT
|
||||
// )}px`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Badge } from "@ui/badge";
|
||||
import {
|
||||
Select,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { MonacoEditor } from "@components/monaco";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Config } from "@components/config";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
import { EndpointConfig } from "./endpoint";
|
||||
import { AlertTypeConfig } from "./alert_types";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ResourceComponents } from "@components/resources";
|
||||
import { ResourceLink } from "@components/resources/common";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { resource_name } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { UsableResource } from "@types";
|
||||
import { Button } from "@ui/button";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { AlerterConfig } from "./config";
|
||||
import { DeleteResource, NewResource } from "../common";
|
||||
import { AlerterTable } from "./table";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { ResourcePageHeader } from "@components/util";
|
||||
|
||||
const useAlerter = (id?: string) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const AlerterTable = ({
|
||||
alerters,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ConfirmButton } from "@components/util";
|
||||
import { useExecute, useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Ban, Hammer, Loader2 } from "lucide-react";
|
||||
import { useBuilder } from "../builder";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
SystemCommand,
|
||||
} from "@components/config/util";
|
||||
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Ban, CirclePlus, PlusCircle } from "lucide-react";
|
||||
import { ReactNode, useState } from "react";
|
||||
@@ -190,98 +190,6 @@ export const BuildConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Webhook",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Komodo",
|
||||
components: {
|
||||
["Guard" as any]: () => {
|
||||
if (update.branch ?? config.branch) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ConfigItem label="Configure Branch">
|
||||
<div>Must configure Branch before webhooks will work.</div>
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
["build" as any]: () => (
|
||||
<ConfigItem label="Webhook Url">
|
||||
<CopyGithubWebhook path={`/build/${id}`} />
|
||||
</ConfigItem>
|
||||
),
|
||||
webhook_enabled: webhook !== undefined && !webhook.managed,
|
||||
webhook_secret: {
|
||||
description:
|
||||
"Provide a custom webhook secret for this resource, or use the global default.",
|
||||
placeholder: "Input custom secret",
|
||||
},
|
||||
["managed" as any]: () => {
|
||||
const inv = useInvalidate();
|
||||
const { toast } = useToast();
|
||||
const { mutate: createWebhook, isPending: createPending } =
|
||||
useWrite("CreateBuildWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Created" });
|
||||
inv(["GetBuildWebhookEnabled", { build: id }]);
|
||||
},
|
||||
});
|
||||
const { mutate: deleteWebhook, isPending: deletePending } =
|
||||
useWrite("DeleteBuildWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Deleted" });
|
||||
inv(["GetBuildWebhookEnabled", { build: id }]);
|
||||
},
|
||||
});
|
||||
if (!webhook || !webhook.managed) return;
|
||||
return (
|
||||
<ConfigItem label="Manage Webhook">
|
||||
{webhook.enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() => deleteWebhook({ build: id })}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhook.enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Critical"
|
||||
)}
|
||||
>
|
||||
DISABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Enable Build"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() => createWebhook({ build: id })}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Links",
|
||||
description: "Add quick links in the resource header",
|
||||
@@ -447,6 +355,98 @@ export const BuildConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Webhook",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Komodo",
|
||||
components: {
|
||||
["Guard" as any]: () => {
|
||||
if (update.branch ?? config.branch) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ConfigItem label="Configure Branch">
|
||||
<div>Must configure Branch before webhooks will work.</div>
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
["build" as any]: () => (
|
||||
<ConfigItem label="Webhook Url">
|
||||
<CopyGithubWebhook path={`/build/${id}`} />
|
||||
</ConfigItem>
|
||||
),
|
||||
webhook_enabled: webhook !== undefined && !webhook.managed,
|
||||
webhook_secret: {
|
||||
description:
|
||||
"Provide a custom webhook secret for this resource, or use the global default.",
|
||||
placeholder: "Input custom secret",
|
||||
},
|
||||
["managed" as any]: () => {
|
||||
const inv = useInvalidate();
|
||||
const { toast } = useToast();
|
||||
const { mutate: createWebhook, isPending: createPending } =
|
||||
useWrite("CreateBuildWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Created" });
|
||||
inv(["GetBuildWebhookEnabled", { build: id }]);
|
||||
},
|
||||
});
|
||||
const { mutate: deleteWebhook, isPending: deletePending } =
|
||||
useWrite("DeleteBuildWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Deleted" });
|
||||
inv(["GetBuildWebhookEnabled", { build: id }]);
|
||||
},
|
||||
});
|
||||
if (!webhook || !webhook.managed) return;
|
||||
return (
|
||||
<ConfigItem label="Manage Webhook">
|
||||
{webhook.enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() => deleteWebhook({ build: id })}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhook.enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Critical"
|
||||
)}
|
||||
>
|
||||
DISABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Enable Build"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() => createWebhook({ build: id })}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { cn } from "@lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
import { ResourceComponents } from "..";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DashboardPieChart } from "@pages/home/dashboard";
|
||||
import { ResourcePageHeader, StatusBadge } from "@components/util";
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { fmt_version } from "@lib/formatting";
|
||||
import { ResourceLink } from "../common";
|
||||
import { BuildComponents } from ".";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const BuildTable = ({ builds }: { builds: Types.BuildListItem[] }) => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Config } from "@components/config";
|
||||
import { ConfigItem, ConfigList } from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
import { ResourceLink, ResourceSelector } from "../common";
|
||||
import { Button } from "@ui/button";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NewLayout } from "@components/layouts";
|
||||
import { useRead, useUser, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { Input } from "@ui/input";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { BuilderInstanceType } from ".";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const BuilderTable = ({
|
||||
builders,
|
||||
|
||||
@@ -31,7 +31,7 @@ import { ResourceComponents } from ".";
|
||||
import { Input } from "@ui/input";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { NewLayout } from "@components/layouts";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { filterBySplit, usableResourcePath } from "@lib/utils";
|
||||
|
||||
export const ResourceDescription = ({
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { Input } from "@ui/input";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -203,7 +203,7 @@ export const StartStopDeployment = ({ id }: DeploymentId) => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (state === Types.DeploymentState.Running) {
|
||||
if (state !== Types.DeploymentState.NotDeployed) {
|
||||
return <StopDeployment id={id} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ResourceSelector } from "@components/resources/common";
|
||||
import { fmt_date, fmt_version } from "@lib/formatting";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { filterBySplit } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { CaretSortIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
Command,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Input } from "@ui/input";
|
||||
import {
|
||||
Select,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { ReactNode, useState } from "react";
|
||||
import {
|
||||
AccountSelectorConfig,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useLocalStorage, useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { HardDrive, Rocket, Server } from "lucide-react";
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Section } from "@components/layouts";
|
||||
import { useLocalStorage, useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { RefreshCw, X, AlertOctagon } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TableTags } from "@components/tags";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { ResourceLink } from "../common";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { Section } from "@components/layouts";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Card, CardHeader } from "@ui/card";
|
||||
import { Input } from "@ui/input";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DashboardPieChart } from "@pages/home/dashboard";
|
||||
|
||||
const useProcedure = (id?: string) =>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { ResourceLink } from "../common";
|
||||
import { ProcedureComponents } from ".";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const ProcedureTable = ({
|
||||
procedures,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { useRepo } from ".";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useBuilder } from "../builder";
|
||||
|
||||
export const CloneRepo = ({ id }: { id: string }) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
SystemCommand,
|
||||
} from "@components/config/util";
|
||||
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
import { CopyGithubWebhook, ResourceLink, ResourceSelector } from "../common";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { cn } from "@lib/utils";
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
|
||||
import { useServer } from "../server";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { DashboardPieChart } from "@pages/home/dashboard";
|
||||
import { ResourcePageHeader, StatusBadge } from "@components/util";
|
||||
import { Badge } from "@ui/badge";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { RepoComponents } from ".";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const RepoTable = ({ repos }: { repos: Types.RepoListItem[] }) => {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
ProviderSelectorConfig,
|
||||
} from "@components/config/util";
|
||||
import { useInvalidate, useLocalStorage, useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { CopyGithubWebhook } from "../common";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
@@ -148,6 +148,22 @@ export const ResourceSyncConfig = ({
|
||||
},
|
||||
};
|
||||
|
||||
const general_common: ConfigComponent<Types.ResourceSyncConfig> = {
|
||||
label: "General",
|
||||
components: {
|
||||
delete: !managed && {
|
||||
label: "Delete Unmatched Resources",
|
||||
description:
|
||||
"Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.",
|
||||
},
|
||||
managed: {
|
||||
label: "Managed",
|
||||
description:
|
||||
"Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const match_tags: ConfigComponent<Types.ResourceSyncConfig> = {
|
||||
label: "Match Tags",
|
||||
description: "Only sync resources matching all of these tags.",
|
||||
@@ -178,16 +194,7 @@ export const ResourceSyncConfig = ({
|
||||
placeholder="Input resource path"
|
||||
/>
|
||||
),
|
||||
delete: !managed && {
|
||||
label: "Delete Unmatched Resources",
|
||||
description:
|
||||
"Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.",
|
||||
},
|
||||
managed: {
|
||||
label: "Managed",
|
||||
description:
|
||||
"Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.",
|
||||
},
|
||||
...general_common.components,
|
||||
},
|
||||
},
|
||||
match_tags,
|
||||
@@ -264,16 +271,7 @@ export const ResourceSyncConfig = ({
|
||||
placeholder="Input resource path"
|
||||
/>
|
||||
),
|
||||
delete: !managed && {
|
||||
label: "Delete Unmatched Resources",
|
||||
description:
|
||||
"Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.",
|
||||
},
|
||||
managed: {
|
||||
label: "Managed",
|
||||
description:
|
||||
"Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.",
|
||||
},
|
||||
...general_common.components,
|
||||
},
|
||||
},
|
||||
match_tags,
|
||||
@@ -471,21 +469,7 @@ export const ResourceSyncConfig = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "General",
|
||||
components: {
|
||||
delete: !managed && {
|
||||
label: "Delete Unmatched Resources",
|
||||
description:
|
||||
"Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.",
|
||||
},
|
||||
managed: {
|
||||
label: "Managed",
|
||||
description:
|
||||
"Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.",
|
||||
},
|
||||
},
|
||||
},
|
||||
general_common,
|
||||
match_tags,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Card } from "@ui/card";
|
||||
import { Clock, FolderSync } from "lucide-react";
|
||||
import { DeleteResource, NewResource } from "../common";
|
||||
import { ResourceSyncTable } from "./table";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { CommitSync, ExecuteSync, RefreshSync } from "./actions";
|
||||
import {
|
||||
border_color_class_by_intention,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { ResourceSyncComponents } from ".";
|
||||
|
||||
export const ResourceSyncTable = ({
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "@ui/dialog";
|
||||
import { useWebsocketMessages } from "@lib/socket";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const LaunchServer = ({ id }: { id: string }) => {
|
||||
const nav = useNavigate();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Config } from "@components/config";
|
||||
import { ConfigList } from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Card } from "@ui/card";
|
||||
import {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Config } from "@components/config";
|
||||
import { ConfigItem, ConfigList } from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { cn, filterBySplit } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Card } from "@ui/card";
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { AwsServerTemplateConfig } from "./aws";
|
||||
import { HetznerServerTemplateConfig } from "./hetzner";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const ServerTemplateConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetServerTemplate", { server_template: id }).data
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ServerTemplateConfig } from "./config";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { useState } from "react";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
import { NewLayout } from "@components/layouts";
|
||||
import { Input } from "@ui/input";
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TableTags } from "@components/tags";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const ServerTemplateTable = ({
|
||||
serverTemplates,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Pen, Scissors } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useServer } from ".";
|
||||
import { has_minimum_permissions } from "@lib/utils";
|
||||
import { Types } from "@komodo/client";
|
||||
import { Types } from "komodo_client";
|
||||
|
||||
export const RenameServer = ({ id }: { id: string }) => {
|
||||
const invalidate = useInvalidate();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user