mirror of
https://github.com/moghtech/komodo.git
synced 2026-05-05 15:34:09 -05:00
[v1.13 - Komodo] Docker compose support with the Stack resource (#24) Co-authored with @karamvirsingh98
* add some network stuff to container summary * improve settings tables UI * periphery build supports additional tags * fix variable container sizing * alert types newline wrap * plumbing for Stack resource * plumbing for Stack resource * mount stack api * stack resource sync * get remote compose file * support image_name and image_tag * add server config placeholders. default server config address * configure image name and image tag * deployment work with build image_name and image_tag * stack UI * fe builds * configure registry provider and account * implement periphery stack api * stack poll interval * add UI provider management * deploy stacks * build push commit hash tag. * Destroy stack * update default core port to 9120 * remove git_account alias * finish stack (and container) api * frontend builds * cant cancel server based builds * fix * use git pull -f * 9120 * start UI updates (#15) * fix From<Stack> for CloneArgs * remove unused imports * UI Updates (#16) * cleanup dashboard charts for resources * bring back solid scrollbars * enable sidebar scrolling * remove alerts from all resources * pass jwt secret * stacks dont delete the target * parse services from yaml * stacks deploy * close * looking good * closer * destroy stack when file missing. onboard stacks * figure out stack container name matching * get stack state correct * work with service views * UI Updates - Sidebar, Topbar Alerts, and All Resources page (#17) * move sidebar to use fixed positioning instead of sticky * add alert details dialog to topbar alerts * cleanup all resources page layout * ensure resource links don't propagate clicks * periphery support passing env with --env-file * StackServicePage * default run_directory to ./ for clarify * add stack webhook listeners * add default compose name of stack name * stacks controlled with project name * migrate to dotenvy * add stack to dashboard * remove deploying / destroying stack services * update config files * fix getting service logs * git / docker provider management api * implement passing git / registry token from db * rename system user Github to Git Webhook * seperate deployed and latest services on stack info * add stack service level operations * UI Updates - Update Shadcn/UI components, prevent navbar menu layout shift (#20) * add dashboard pie for resource syncs * dashboard items same height * update shadcn components * ensure centered following sheet update * cleanup layout, prevent navbar menu layout shifts * add manual filter, fix toast call * guard webhooks * remove deployed_message, latest_message from StackListItemInfo * stop all containers on server correctly * support multiple compose files * cache all containers networks images projects * remove project missing from db cache * work on sync deploy stuff * rework deployment sync deploy to support stacks. they can depend on each other. * UI Updates - Remove topbar transparency, pretty status badges, tidy resource page layout with a 'back' button (#21) * remove topbar transparency * cleanup unused * responsive dashboard * better mobile header * dont need to calc 64px less since header is using position fixed * add status badge component * update status badges * further simplify layout * allow undefined status as prop * use new status badges for alerts * update status badges for all resources * undo layout change * tidy up resource page layout, add back button * no need for button wrapper * remove unused * build cancel log * update ts types * fix fe type changes * fe tweaks * remove on build logs * core refresh cache immediately on startup * jwt_ttl * canonicalize run directory on host * update canonicalize error message * core use docker-compose * fix incorrect project missing, add status string to stack info * remove entries in "after" that aren't deploying * fix dockerfiel * build custom tag postfix * sync fixes * ensure UpdateGitProviderAccount doesn't change id * ensure UpdateDockerRegistryAccount doesn't change id * configure providers in the UI * add // comment support to env, conversions * add updates for provider deletes * improve sync pending deploy log * add more deployment actions * add backward compat with v1.12 for clone repo * stack deploy format * fe * alert menus clone when click resource link * rename stacks * don't close on click * snake case stack state, in line with deployment state * sync redeploy stack if newer hash (optional behind resource field 'latest_hash') * remove nav to tree * RefreshStack/Sync debug instruments * improve inline UI docs * implement resource base_permission backend * plumbing for Repo build * build repos * write env file repos * add latest hash / message to build info * add optional hash to update * keep built_hash updated * add backend for build / repo latest hash management * remove unused resources * clean up repo dirs after cache update * fix repo info deser error * add build / repo git status * fix page layouts * improve layout responsive * most config incline docs * add descriptions for all resource types * default local auth false * fix omnibar arrow keys issue * add compose file to example config * image registry * dashboard display no resources messge * update deps. * show when no config * resource sync use config git_provider * fix networks * fix deploy error due to after * update lots of docs * fix server stat charts not working * update screenshots * update changelog * add a disclaimer * remove file paths docs stuff * build repo * v1.13 - Komodo * update docs for cli * fill out the compose example more --------- Co-authored-by: Karamvir Singh <67458484+karamvirsingh98@users.noreply.github.com>
This commit is contained in:
353
bin/core/src/api/execute/stack.rs
Normal file
353
bin/core/src/api/execute/stack.rs
Normal file
@@ -0,0 +1,353 @@
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
permission::PermissionLevel, stack::StackInfo, update::Update,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_document};
|
||||
use periphery_client::api::compose::*;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
interpolate_variables_secrets_into_environment, periphery_client,
|
||||
stack::{
|
||||
execute::execute_compose, get_stack_and_server,
|
||||
json::get_config_jsons, services::extract_services_into_res,
|
||||
},
|
||||
update::update_update,
|
||||
},
|
||||
monitor::update_cache_for_server,
|
||||
state::{action_states, db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<DeployStack, (User, Update)> for State {
|
||||
#[instrument(name = "DeployStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeployStack { stack, stop_time }: DeployStack,
|
||||
(user, mut update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let (mut stack, server) = get_stack_and_server(
|
||||
&stack,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// get the action state for the stack (or insert default).
|
||||
let action_state =
|
||||
action_states().stack.get_or_insert_default(&stack.id).await;
|
||||
|
||||
// Will check to ensure stack not already busy before updating, and return Err if so.
|
||||
// The returned guard will set the action state back to default when dropped.
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.deploying = true)?;
|
||||
|
||||
let git_token = crate::helpers::git_token(
|
||||
&stack.config.git_provider,
|
||||
&stack.config.git_account,
|
||||
|https| stack.config.git_https = https,
|
||||
).await.with_context(
|
||||
|| format!("Failed to get git token in call to db. Stopping run. | {} | {}", stack.config.git_provider, stack.config.git_account),
|
||||
)?;
|
||||
|
||||
let registry_token = crate::helpers::registry_token(
|
||||
&stack.config.registry_provider,
|
||||
&stack.config.registry_account,
|
||||
).await.with_context(
|
||||
|| format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account),
|
||||
)?;
|
||||
|
||||
if !stack.config.skip_secret_interp {
|
||||
interpolate_variables_secrets_into_environment(
|
||||
&mut stack.config.environment,
|
||||
&mut update,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let ComposeUpResponse {
|
||||
logs,
|
||||
deployed,
|
||||
file_contents,
|
||||
missing_files,
|
||||
remote_errors,
|
||||
commit_hash,
|
||||
commit_message,
|
||||
} = periphery_client(&server)?
|
||||
.request(ComposeUp {
|
||||
stack: stack.clone(),
|
||||
service: None,
|
||||
git_token,
|
||||
registry_token,
|
||||
})
|
||||
.await?;
|
||||
|
||||
update.logs.extend(logs);
|
||||
|
||||
let update_info = async {
|
||||
let (latest_services, json, json_errors) = if !file_contents
|
||||
.is_empty()
|
||||
{
|
||||
let (jsons, json_errors) =
|
||||
get_config_jsons(&file_contents).await;
|
||||
let mut services = Vec::new();
|
||||
for contents in &file_contents {
|
||||
if let Err(e) = extract_services_into_res(
|
||||
&stack.project_name(true),
|
||||
&contents.contents,
|
||||
&mut services,
|
||||
) {
|
||||
update.push_error_log(
|
||||
"extract services",
|
||||
format_serror(&e.context(format!("Failed to extract stack services for compose file path {}. Things probably won't work correctly", contents.path)).into())
|
||||
);
|
||||
}
|
||||
}
|
||||
(services, jsons, json_errors)
|
||||
} else {
|
||||
// maybe better to do something else here for services.
|
||||
(stack.info.latest_services.clone(), Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
let project_name = stack.project_name(true);
|
||||
|
||||
let (
|
||||
deployed_services,
|
||||
deployed_contents,
|
||||
deployed_json,
|
||||
deployed_json_errors,
|
||||
deployed_hash,
|
||||
deployed_message,
|
||||
) = if deployed {
|
||||
(
|
||||
Some(latest_services.clone()),
|
||||
Some(file_contents.clone()),
|
||||
Some(json.clone()),
|
||||
Some(json_errors.clone()),
|
||||
commit_hash.clone(),
|
||||
commit_message.clone(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
stack.info.deployed_services,
|
||||
stack.info.deployed_contents,
|
||||
stack.info.deployed_json,
|
||||
stack.info.deployed_json_errors,
|
||||
stack.info.deployed_hash,
|
||||
stack.info.deployed_message,
|
||||
)
|
||||
};
|
||||
|
||||
let info = StackInfo {
|
||||
missing_files,
|
||||
deployed_project_name: project_name.into(),
|
||||
deployed_services,
|
||||
deployed_contents,
|
||||
deployed_hash,
|
||||
deployed_message,
|
||||
deployed_json,
|
||||
deployed_json_errors,
|
||||
latest_services,
|
||||
latest_json: json,
|
||||
latest_json_errors: json_errors,
|
||||
remote_contents: stack
|
||||
.config
|
||||
.file_contents
|
||||
.is_empty()
|
||||
.then_some(file_contents),
|
||||
remote_errors: stack
|
||||
.config
|
||||
.file_contents
|
||||
.is_empty()
|
||||
.then_some(remote_errors),
|
||||
latest_hash: commit_hash,
|
||||
latest_message: commit_message,
|
||||
};
|
||||
|
||||
let info = to_document(&info)
|
||||
.context("failed to serialize stack info to bson")?;
|
||||
|
||||
db_client()
|
||||
.await
|
||||
.stacks
|
||||
.update_one(
|
||||
doc! { "name": &stack.name },
|
||||
doc! { "$set": { "info": info } },
|
||||
)
|
||||
.await
|
||||
.context("failed to update stack info on db")?;
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
// This will be weird with single service deploys. Come back to it.
|
||||
if let Err(e) = update_info.await {
|
||||
update.push_error_log(
|
||||
"refresh stack info",
|
||||
format_serror(
|
||||
&e.context("failed to refresh stack info on db").into(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Ensure cached stack state up to date by updating server cache
|
||||
update_cache_for_server(&server).await;
|
||||
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<StartStack, (User, Update)> for State {
|
||||
#[instrument(name = "StartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StartStack { stack, service }: StartStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<StartStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.starting = true
|
||||
}
|
||||
},
|
||||
update,
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<RestartStack, (User, Update)> for State {
|
||||
#[instrument(name = "RestartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RestartStack { stack, service }: RestartStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<RestartStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.restarting = true;
|
||||
}
|
||||
},
|
||||
update,
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<PauseStack, (User, Update)> for State {
|
||||
#[instrument(name = "PauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PauseStack { stack, service }: PauseStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<PauseStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.pausing = true
|
||||
}
|
||||
},
|
||||
update,
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<UnpauseStack, (User, Update)> for State {
|
||||
#[instrument(name = "UnpauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
UnpauseStack { stack, service }: UnpauseStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<UnpauseStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.unpausing = true
|
||||
}
|
||||
},
|
||||
update,
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<StopStack, (User, Update)> for State {
|
||||
#[instrument(name = "StopStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopStack {
|
||||
stack,
|
||||
stop_time,
|
||||
service,
|
||||
}: StopStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<StopStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.stopping = true
|
||||
}
|
||||
},
|
||||
update,
|
||||
stop_time,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DestroyStack, (User, Update)> for State {
|
||||
#[instrument(name = "DestroyStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DestroyStack {
|
||||
stack,
|
||||
remove_orphans,
|
||||
stop_time,
|
||||
}: DestroyStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
execute_compose::<DestroyStack>(
|
||||
&stack,
|
||||
None,
|
||||
&user,
|
||||
|state| state.destroying = true,
|
||||
update,
|
||||
(stop_time, remove_orphans),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user